From d8d5631734c3e0fa848f3dc192036b74bc804cad Mon Sep 17 00:00:00 2001 From: Max Weber Date: Fri, 15 May 2020 02:08:32 -0600 Subject: [PATCH] IconTextField: add combobox-style suggestion dropdown --- .../client/ui/components/IconTextField.java | 144 +++++++++++++++--- 1 file changed, 119 insertions(+), 25 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/ui/components/IconTextField.java b/runelite-client/src/main/java/net/runelite/client/ui/components/IconTextField.java index 0408837302..0266caa450 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/components/IconTextField.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/components/IconTextField.java @@ -31,25 +31,36 @@ import java.awt.Color; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.function.Consumer; +import javax.swing.DefaultListModel; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JLabel; +import javax.swing.JList; import javax.swing.JPanel; +import javax.swing.JPopupMenu; import javax.swing.JTextField; +import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; import javax.swing.text.Document; import lombok.Getter; import lombok.RequiredArgsConstructor; import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.FontManager; +import net.runelite.client.util.SwingUtil; +import org.apache.commons.lang3.StringUtils; +import org.pushingpixels.substance.internal.ui.SubstanceListUI; /** * This component is a FlatTextField with an icon on its left side, and a clear button (×) on its right side. @@ -58,10 +69,13 @@ public class IconTextField extends JPanel { // To support gifs, the icon needs to be wrapped in a JLabel private final JLabel iconWrapperLabel; - private final FlatTextField textField; private final JButton clearButton; + private final JButton suggestionButton; + + @Getter + private final DefaultListModel suggestionListModel; public IconTextField() { @@ -108,66 +122,146 @@ public class IconTextField extends JPanel textField.addMouseListener(hoverEffect); innerTxt.addMouseListener(hoverEffect); - clearButton = new JButton("×"); - clearButton.setPreferredSize(new Dimension(30, 0)); - clearButton.setFont(FontManager.getRunescapeBoldFont()); - clearButton.setForeground(ColorScheme.PROGRESS_ERROR_COLOR); - clearButton.setBorder(null); - clearButton.setBorderPainted(false); - clearButton.setContentAreaFilled(false); - clearButton.setVisible(false); - - // ActionListener for keyboard use (via Tab -> Space) + clearButton = createRHSButton(ColorScheme.PROGRESS_ERROR_COLOR, Color.PINK); + clearButton.setText("×"); clearButton.addActionListener(evt -> setText(null)); - // MouseListener for hover and click events - clearButton.addMouseListener(new MouseAdapter() + suggestionListModel = new DefaultListModel<>(); + suggestionListModel.addListDataListener(new ListDataListener() { @Override - public void mousePressed(MouseEvent mouseEvent) + public void intervalAdded(ListDataEvent e) { - setText(null); + updateContextButton(); } @Override - public void mouseEntered(MouseEvent mouseEvent) + public void intervalRemoved(ListDataEvent e) { - clearButton.setForeground(Color.PINK); - textField.dispatchEvent(mouseEvent); + updateContextButton(); } @Override - public void mouseExited(MouseEvent mouseEvent) + public void contentsChanged(ListDataEvent e) { - clearButton.setForeground(ColorScheme.PROGRESS_ERROR_COLOR); - textField.dispatchEvent(mouseEvent); + updateContextButton(); } }); + JList suggestionList = new JList<>(); + suggestionList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + suggestionList.setModel(suggestionListModel); + suggestionList.addListSelectionListener(e -> + { + String val = suggestionList.getSelectedValue(); + if (val == null) + { + return; + } + + textField.setText(val); + textField.getTextField().selectAll(); + textField.getTextField().requestFocusInWindow(); + }); + + JPopupMenu popup = new JPopupMenu(); + popup.setLightWeightPopupEnabled(true); + popup.setLayout(new BorderLayout()); + popup.add(suggestionList, BorderLayout.CENTER); + popup.addFocusListener(new FocusAdapter() + { + @Override + public void focusLost(FocusEvent e) + { + popup.setVisible(false); + suggestionList.clearSelection(); + + SubstanceListUI ui = (SubstanceListUI) suggestionList.getUI(); + ui.resetRolloverIndex(); + } + }); + + suggestionButton = createRHSButton(ColorScheme.LIGHT_GRAY_COLOR, ColorScheme.MEDIUM_GRAY_COLOR); + suggestionButton.setText("▾"); + suggestionButton.addActionListener(e -> + { + popup.setPopupSize(getWidth(), suggestionList.getPreferredSize().height); + popup.show(IconTextField.this, 0, suggestionButton.getHeight()); + popup.revalidate(); + popup.requestFocusInWindow(); + }); + // Show the clear button when text is present, and hide again when empty textField.getTextField().getDocument().addDocumentListener(new DocumentListener() { @Override public void insertUpdate(DocumentEvent e) { - SwingUtilities.invokeLater(() -> clearButton.setVisible(true)); + updateContextButton(); } @Override public void removeUpdate(DocumentEvent e) { - SwingUtilities.invokeLater(() -> clearButton.setVisible(!getText().isEmpty())); + updateContextButton(); } @Override public void changedUpdate(DocumentEvent e) { + updateContextButton(); } }); + JPanel rhsButtons = new JPanel(); + rhsButtons.setBackground(new Color(0, 0, 0, 0)); + rhsButtons.setOpaque(false); + rhsButtons.setLayout(new BorderLayout()); + rhsButtons.add(clearButton, BorderLayout.EAST); + rhsButtons.add(suggestionButton, BorderLayout.WEST); + updateContextButton(); + add(iconWrapperLabel, BorderLayout.WEST); add(textField, BorderLayout.CENTER); - add(clearButton, BorderLayout.EAST); + add(rhsButtons, BorderLayout.EAST); + } + + private JButton createRHSButton(Color fg, Color rollover) + { + JButton b = new JButton(); + b.setPreferredSize(new Dimension(30, 0)); + b.setFont(FontManager.getRunescapeBoldFont()); + b.setBorder(null); + b.setRolloverEnabled(true); + SwingUtil.removeButtonDecorations(b); + b.setForeground(fg); + + b.addMouseListener(new MouseAdapter() + { + @Override + public void mouseEntered(MouseEvent mouseEvent) + { + b.setForeground(rollover); + textField.dispatchEvent(mouseEvent); + } + + @Override + public void mouseExited(MouseEvent mouseEvent) + { + b.setForeground(fg); + textField.dispatchEvent(mouseEvent); + } + }); + + return b; + } + + private void updateContextButton() + { + boolean empty = StringUtils.isBlank(textField.getText()); + + clearButton.setVisible(!empty); + suggestionButton.setVisible(!suggestionListModel.isEmpty() && empty); } public void addActionListener(ActionListener actionListener) @@ -188,6 +282,7 @@ public class IconTextField extends JPanel public void setText(String text) { + assert SwingUtilities.isEventDispatchThread(); textField.setText(text); } @@ -290,5 +385,4 @@ public class IconTextField extends JPanel private final String file; } - }