Related
I would like to add a way to scroll through menu items in a JPopupMenu, much like scrolling through a list of items in a JComboBox.
Let's say I have 10 menu items. I would like to display only 5 menu items at a time, and I would use a vertical scroll button at the bottom or top of the JPopupMenu to show the menu items that are not listed and hide the ones that I just saw.
Is it possible? I am using JIDE Software's JideSplitButton, which displays a JPopupMenu when clicked. I am trying to keep the look and feel of the command bar on which I placed the JideSplitButton, so I don't want to replace it with a JComboBox unless I really have to.
Here is a version I created using a scrollbar. It is just a simple example, so adapt as you see fit:
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import javax.swing.JPopupMenu;
import javax.swing.JScrollBar;
public class JScrollPopupMenu extends JPopupMenu {
protected int maximumVisibleRows = 10;
public JScrollPopupMenu() {
this(null);
}
public JScrollPopupMenu(String label) {
super(label);
setLayout(new ScrollPopupMenuLayout());
super.add(getScrollBar());
addMouseWheelListener(new MouseWheelListener() {
#Override public void mouseWheelMoved(MouseWheelEvent event) {
JScrollBar scrollBar = getScrollBar();
int amount = (event.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL)
? event.getUnitsToScroll() * scrollBar.getUnitIncrement()
: (event.getWheelRotation() < 0 ? -1 : 1) * scrollBar.getBlockIncrement();
scrollBar.setValue(scrollBar.getValue() + amount);
event.consume();
}
});
}
private JScrollBar popupScrollBar;
protected JScrollBar getScrollBar() {
if(popupScrollBar == null) {
popupScrollBar = new JScrollBar(JScrollBar.VERTICAL);
popupScrollBar.addAdjustmentListener(new AdjustmentListener() {
#Override public void adjustmentValueChanged(AdjustmentEvent e) {
doLayout();
repaint();
}
});
popupScrollBar.setVisible(false);
}
return popupScrollBar;
}
public int getMaximumVisibleRows() {
return maximumVisibleRows;
}
public void setMaximumVisibleRows(int maximumVisibleRows) {
this.maximumVisibleRows = maximumVisibleRows;
}
public void paintChildren(Graphics g){
Insets insets = getInsets();
g.clipRect(insets.left, insets.top, getWidth(), getHeight() - insets.top - insets.bottom);
super.paintChildren(g);
}
protected void addImpl(Component comp, Object constraints, int index) {
super.addImpl(comp, constraints, index);
if(maximumVisibleRows < getComponentCount()-1) {
getScrollBar().setVisible(true);
}
}
public void remove(int index) {
// can't remove the scrollbar
++index;
super.remove(index);
if(maximumVisibleRows >= getComponentCount()-1) {
getScrollBar().setVisible(false);
}
}
public void show(Component invoker, int x, int y){
JScrollBar scrollBar = getScrollBar();
if(scrollBar.isVisible()){
int extent = 0;
int max = 0;
int i = 0;
int unit = -1;
int width = 0;
for(Component comp : getComponents()) {
if(!(comp instanceof JScrollBar)) {
Dimension preferredSize = comp.getPreferredSize();
width = Math.max(width, preferredSize.width);
if(unit < 0){
unit = preferredSize.height;
}
if(i++ < maximumVisibleRows) {
extent += preferredSize.height;
}
max += preferredSize.height;
}
}
Insets insets = getInsets();
int widthMargin = insets.left + insets.right;
int heightMargin = insets.top + insets.bottom;
scrollBar.setUnitIncrement(unit);
scrollBar.setBlockIncrement(extent);
scrollBar.setValues(0, heightMargin + extent, 0, heightMargin + max);
width += scrollBar.getPreferredSize().width + widthMargin;
int height = heightMargin + extent;
setPopupSize(new Dimension(width, height));
}
super.show(invoker, x, y);
}
protected static class ScrollPopupMenuLayout implements LayoutManager{
#Override public void addLayoutComponent(String name, Component comp) {}
#Override public void removeLayoutComponent(Component comp) {}
#Override public Dimension preferredLayoutSize(Container parent) {
int visibleAmount = Integer.MAX_VALUE;
Dimension dim = new Dimension();
for(Component comp :parent.getComponents()){
if(comp.isVisible()) {
if(comp instanceof JScrollBar){
JScrollBar scrollBar = (JScrollBar) comp;
visibleAmount = scrollBar.getVisibleAmount();
}
else {
Dimension pref = comp.getPreferredSize();
dim.width = Math.max(dim.width, pref.width);
dim.height += pref.height;
}
}
}
Insets insets = parent.getInsets();
dim.height = Math.min(dim.height + insets.top + insets.bottom, visibleAmount);
return dim;
}
#Override public Dimension minimumLayoutSize(Container parent) {
int visibleAmount = Integer.MAX_VALUE;
Dimension dim = new Dimension();
for(Component comp : parent.getComponents()) {
if(comp.isVisible()){
if(comp instanceof JScrollBar) {
JScrollBar scrollBar = (JScrollBar) comp;
visibleAmount = scrollBar.getVisibleAmount();
}
else {
Dimension min = comp.getMinimumSize();
dim.width = Math.max(dim.width, min.width);
dim.height += min.height;
}
}
}
Insets insets = parent.getInsets();
dim.height = Math.min(dim.height + insets.top + insets.bottom, visibleAmount);
return dim;
}
#Override public void layoutContainer(Container parent) {
Insets insets = parent.getInsets();
int width = parent.getWidth() - insets.left - insets.right;
int height = parent.getHeight() - insets.top - insets.bottom;
int x = insets.left;
int y = insets.top;
int position = 0;
for(Component comp : parent.getComponents()) {
if((comp instanceof JScrollBar) && comp.isVisible()) {
JScrollBar scrollBar = (JScrollBar) comp;
Dimension dim = scrollBar.getPreferredSize();
scrollBar.setBounds(x + width-dim.width, y, dim.width, height);
width -= dim.width;
position = scrollBar.getValue();
}
}
y -= position;
for(Component comp : parent.getComponents()) {
if(!(comp instanceof JScrollBar) && comp.isVisible()) {
Dimension pref = comp.getPreferredSize();
comp.setBounds(x, y, width, pref.height);
y += pref.height;
}
}
}
}
}
In addition to the JScrollPopupMenu above, I also needed a a scroll bar in a sub menu (aka a "Pull Right Menu.") This seems to be a more common case. So I adapted a JMenu to use the JScrollPopupMenu called JScrollMenu:
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.MenuElement;
import javax.swing.UIManager;
import javax.swing.plaf.MenuItemUI;
import javax.swing.plaf.PopupMenuUI;
import java.awt.Component;
import java.awt.ComponentOrientation;
public class JScrollMenu extends JMenu {
// Covers the one in the JMenu because the method that creates it in JMenu is private
/** The popup menu portion of the menu.*/
private JPopupMenu popupMenu;
/**
* Constructs a new <code>JMenu</code> with no text.
*/
public JScrollMenu() {
this("");
}
/**
* Constructs a new <code>JMenu</code> with the supplied string as its text.
*
* #param s the text for the menu label
*/
public JScrollMenu(String s) {
super(s);
}
/**
* Constructs a menu whose properties are taken from the <code>Action</code> supplied.
*
* #param a an <code>Action</code>
*/
public JScrollMenu(Action a) {
this();
setAction(a);
}
/**
* Lazily creates the popup menu. This method will create the popup using the <code>JScrollPopupMenu</code> class.
*/
protected void ensurePopupMenuCreated() {
if(popupMenu == null) {
this.popupMenu = new JScrollPopupMenu();
popupMenu.setInvoker(this);
popupListener = createWinListener(popupMenu);
}
}
//////////////////////////////
//// All of these methods are necessary because ensurePopupMenuCreated() is private in JMenu
//////////////////////////////
#Override
public void updateUI() {
setUI((MenuItemUI) UIManager.getUI(this));
if(popupMenu != null) {
popupMenu.setUI((PopupMenuUI) UIManager.getUI(popupMenu));
}
}
#Override
public boolean isPopupMenuVisible() {
ensurePopupMenuCreated();
return popupMenu.isVisible();
}
#Override
public void setMenuLocation(int x, int y) {
super.setMenuLocation(x, y);
if(popupMenu != null) {
popupMenu.setLocation(x, y);
}
}
#Override
public JMenuItem add(JMenuItem menuItem) {
ensurePopupMenuCreated();
return popupMenu.add(menuItem);
}
#Override
public Component add(Component c) {
ensurePopupMenuCreated();
popupMenu.add(c);
return c;
}
#Override
public Component add(Component c, int index) {
ensurePopupMenuCreated();
popupMenu.add(c, index);
return c;
}
#Override
public void addSeparator() {
ensurePopupMenuCreated();
popupMenu.addSeparator();
}
#Override
public void insert(String s, int pos) {
if(pos < 0) {
throw new IllegalArgumentException("index less than zero.");
}
ensurePopupMenuCreated();
popupMenu.insert(new JMenuItem(s), pos);
}
#Override
public JMenuItem insert(JMenuItem mi, int pos) {
if(pos < 0) {
throw new IllegalArgumentException("index less than zero.");
}
ensurePopupMenuCreated();
popupMenu.insert(mi, pos);
return mi;
}
#Override
public JMenuItem insert(Action a, int pos) {
if(pos < 0) {
throw new IllegalArgumentException("index less than zero.");
}
ensurePopupMenuCreated();
JMenuItem mi = new JMenuItem(a);
mi.setHorizontalTextPosition(JButton.TRAILING);
mi.setVerticalTextPosition(JButton.CENTER);
popupMenu.insert(mi, pos);
return mi;
}
#Override
public void insertSeparator(int index) {
if(index < 0) {
throw new IllegalArgumentException("index less than zero.");
}
ensurePopupMenuCreated();
popupMenu.insert(new JPopupMenu.Separator(), index);
}
#Override
public void remove(JMenuItem item) {
if(popupMenu != null){
popupMenu.remove(item);
}
}
#Override
public void remove(int pos) {
if(pos < 0) {
throw new IllegalArgumentException("index less than zero.");
}
if(pos > getItemCount()) {
throw new IllegalArgumentException("index greater than the number of items.");
}
if(popupMenu != null){
popupMenu.remove(pos);
}
}
#Override
public void remove(Component c) {
if(popupMenu != null){
popupMenu.remove(c);
}
}
#Override
public void removeAll() {
if(popupMenu != null){
popupMenu.removeAll();
}
}
#Override
public int getMenuComponentCount() {
return (popupMenu == null) ? 0 : popupMenu.getComponentCount();
}
#Override
public Component getMenuComponent(int n) {
return (popupMenu == null) ? null : popupMenu.getComponent(n);
}
#Override
public Component[] getMenuComponents() {
return (popupMenu == null) ? new Component[0] : popupMenu.getComponents();
}
#Override
public JPopupMenu getPopupMenu() {
ensurePopupMenuCreated();
return popupMenu;
}
#Override
public MenuElement[] getSubElements() {
return popupMenu == null ? new MenuElement[0] : new MenuElement[]{popupMenu};
}
#Override
public void applyComponentOrientation(ComponentOrientation o) {
super.applyComponentOrientation(o);
if(popupMenu != null) {
int ncomponents = getMenuComponentCount();
for(int i = 0; i < ncomponents; ++i) {
getMenuComponent(i).applyComponentOrientation(o);
}
popupMenu.setComponentOrientation(o);
}
}
#Override
public void setComponentOrientation(ComponentOrientation o) {
super.setComponentOrientation(o);
if(popupMenu != null) {
popupMenu.setComponentOrientation(o);
}
}
}
Here's another one I found very useful: https://tips4java.wordpress.com/2009/02/01/menu-scroller/
It can be called on JMenu or JPopupMenu like this:
MenuScroller.setScrollerFor(menuInstance, 8, 125, 3, 1);
May be this
http://www.javabeginner.com/java-swing/java-scrollable-popup-menu
Basically you can add any JComponents to the JPopupMenu, you can add JScrollpane to the JPopup by nesting JPanel / JList with another JComponents,
Notice but there is rule that swing GUI doesn't allowing two lightweight popup window in same time, best example is common Bug in Swing about JComboBox in the JPopup
you have look at JWindow, create once time and re_use that for another Action, nothing best around as to check how popup JWindow really works for JCalendar by Kai Toedter
Alternately you may want to consider JidePopupMenu: Scrollable JPopupMenu
As I needed popup menu with Scrollbar, I just reused popup menu from JComboBox.
The trick was to put JComboBox in a JViewport, so that only arrow button was visible. You may make it just one pixel small or even lesser and use event from JideSplitButton to open popup.
You may find code on github.
Adding to the JScrollPopupMenu answer above (I can't edit it).
In order to have it scroll on arrow navigation etc., I added this:
#Override public void scrollRectToVisible(Rectangle aRect) {
final Insets insets = getInsets();
final int scrollY = popupScrollBar.getValue();
int y = aRect.y;
if (y - insets.top < 0)
popupScrollBar.setValue(scrollY + y - insets.top);
else {
y += aRect.height;
final int bottom = getHeight() - insets.bottom;
if (y > bottom)
popupScrollBar.setValue(scrollY + y - bottom);
}
}
Which then be called like so:
popupMenu.scrollRectToVisible(menuItem.getBounds());
i have a problem with the DS Desktop Notify library
in this library, all of notify banners has left to right orientation
i want to change the direction of these notifies from LTR to RTL
(the icons most be in the right side of the banner, also title and message need to start from right side of the banner
how can i do this in this library?
https://i.imgur.com/uTlOpqs.png "screenshot"
i have tried using the "applyComponentOrientation" for the frame, but it's not working
private static class DesktopLayoutFrame extends JDialog {
Image bg;
boolean nativeTrans;
boolean finished=true;
boolean clicked=false;
public DesktopLayoutFrame() {
super((JFrame)null,"DesktopLayoutFrame");
setUndecorated(true);
nativeTrans=Utils.isTranslucencySupported();
setBackground(new Color(0,0,0,nativeTrans? 0:255));
setContentPane(new JComponent(){
#Override
public void paintComponent(Graphics g){
render((Graphics2D)g);
}
});
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent evt){
clicked=true;
}
});
setFocusableWindowState(false);
setAlwaysOnTop(true);
System.out.println("Desktop Notify Frame deployed.");
}
#Override
public void setVisible(boolean visible) {
boolean bool=isVisible();
if (visible) {
Rectangle screenSize = Utils.getScreenSize();
setBounds(screenSize.x+screenSize.width-310, screenSize.y,
305, screenSize.height-5);
if (!bool && !nativeTrans)
bg=Utils.getBackgroundCap(getBounds());
}
super.setVisible(visible);
}
/**
* Paints the window contents.
* #param rd a graphics2D object received from the original paint event.
*/
public void render(Graphics2D rd) {
Point p = getMousePosition();
finished = false;
int x = 0, y = getHeight();
long l = System.currentTimeMillis();
if (windows.isEmpty()) finished = true;
int cur = Cursor.DEFAULT_CURSOR;
if (!nativeTrans) rd.drawImage(bg, 0, 0, this);
for (int i = 0; i < windows.size(); i++) {
DesktopNotify window = windows.get(i);
if (window.isVisible()) {
y -= window.h;
if (window.popupStart == 0) {
window.popupStart = System.currentTimeMillis();
}
if (y > 0) {
boolean hover = false;
if (p != null) {
if (p.y > y && p.y < y + window.h) {
hover = true;
if (window.getAction() != null) {
cur = Cursor.HAND_CURSOR;
}
if (clicked) {
if (window.getAction() != null) {
final DesktopNotify w = window;
final long lf = l;
java.awt.EventQueue.invokeLater(new Runnable(){#Override public void run(){
w.getAction().actionPerformed(new ActionEvent(w, ActionEvent.ACTION_PERFORMED, "fireAction", lf, 0));
}});
}
if (window.expTime() == Long.MAX_VALUE) {
window.timeOut = l - window.popupStart + 500;
}
}
}
}
window.render(x, y, hover, rd, l);
if (window.markedForHide) {
window.timeOut = l - window.popupStart + 500;
window.markedForHide = false;
}
} else {
window.popupStart = l;
}
if (l > window.expTime() || (y <= 0 && window.markedForHide)) {
window.markedForHide = false;
window.setVisible(false);
windows.remove(window);
i--;
}
y -= 5;
}
}
clicked = false;
setCursor(new Cursor(cur));
}
}
i think above code in "DesktopNotifyDriver.java" has to be edited for this. sorry for my english
Please Help Me Through This
Hi I made a pause menu for my game, and you navigate through it with the arrow keys on the keyboard. My question is how do I make it so I can navigate with my mouse, and click the buttons rather then having to use the arrow keys?
here is the code:
public class InGameMenu implements KeyListener {
private String[] string = { "Resume Game", "Options", "Save Game", "Load Game", "Exit Game" };
private String[] optionStrings = { "Help", "Back" };
public static int selected = 0;
private int space = 25;
public int width = 800;
public int height = 600;
public static boolean isInMenu = true;
public static boolean isInOptions = false;
public static boolean saving = false;
public static boolean loading = false;
public InGameMenu() {
}
public void tick() {
}
public void render(Graphics g) {
g.setColor(new Color(0, 0, 0, 90));
g.fillRect(0, 0, Component.width, Component.height);
if (isInMenu) {
g.setColor(Color.LIGHT_GRAY);
if (saving) {
g.drawString("Saving", Component.width / 2 / Component.pixelSize - (int) (Component.width / 25), 35);
}
if (loading) {
g.drawString("Loading", Component.width / 2 / Component.pixelSize - (int) (Component.width / 25), 35);
}
for (int i = 0; i < string.length; i++) {
if (selected == i) {
g.setColor(Color.RED);
} else {
g.setColor(Color.WHITE);
}
g.drawString(string[i], Component.width / 2 / Component.pixelSize - (int) (Component.width / 17.5), Component.height / 8 + (i * space));
}
} else if (isInOptions) {
for (int i = 0; i < optionStrings.length; i++) {
if (selected == i) {
g.setColor(Color.RED);
} else {
g.setColor(Color.WHITE);
}
g.drawString(optionStrings[i], Component.width / 2 / Component.pixelSize - (int) (Component.width / 17.5), Component.height / 8 + (i * space));
}
}
}
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
if (isInMenu) {
if (key == KeyEvent.VK_UP) {
selected--;
if (selected < 0) {
selected = string.length - 1;
}
}
if (key == KeyEvent.VK_DOWN) {
selected++;
if (selected > string.length - 1) {
selected = 0;
}
}
if (key == KeyEvent.VK_ENTER) {
if (selected == 0) {
Component.isInMenu = false;
} else if (selected == 1) {
isInMenu = false;
isInOptions = true;
selected = 0;
} else if (selected == 2) {
saving = true;
SaveLoad.save();
saving = false;
} else if (selected == 3) {
loading = true;
SaveLoad.load();
loading = false;
} else if (selected == 4) {
System.exit(0);
}
}
} else if (isInOptions) {
if (key == KeyEvent.VK_UP) {
selected--;
if (selected < 0) {
selected = optionStrings.length - 1;
}
}
if (key == KeyEvent.VK_DOWN) {
selected++;
if (selected > optionStrings.length - 1) {
selected = 0;
}
}
if (key == KeyEvent.VK_ENTER) {
if (selected == 0) {
System.out.println("HELP");
} else if (selected == 1) {
isInOptions = false;
isInMenu = true;
}
}
}
}
public void keyReleased(KeyEvent e) {
}
public void keyTyped(KeyEvent e) {
}
}
You can implement MouseListener too.
You can add these methods from MouseListener:
public void mousePressed(MouseEvent e) {
if(e.getSource() == button1)
{
isInMenu = false;
isInOptions = true;
selected = 0;
}
if(e.getSource() == button2)
{
saving = true;
SaveLoad.save();
saving = false;
}
if(e.getSource() == button3)
{
loading = true;
SaveLoad.load();
loading = false;
}
if(e.getSource() == button4)
{
System.exit(0);
}
}
public void mouseReleased(MouseEvent e) {
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
public void mouseClicked(MouseEvent e) {
}
First, you must get the bounds (x, y, width and height) of the text.
You already have the x and y:
g.drawString(string[i], Component.width / 2 / Component.pixelSize - (int) (Component.width / 17.5), Component.height / 8 + (i * space));
// x = Component.width / 2 / Component.pixelSize - (int) (Component.width / 17.5)
// y = Component.height / 8 + (i * space)
You can determine the width and height via Font#getStringBounds(String, FontRenderContext):
FontRenderContext renderContext = new FontRenderContext(null, true, true);
Font font = new Font("Arial", Font.PLAIN, 14);
String labelText = "Start";
Rectangle2D labelBounds = font.getStringBounds(labelText, renderContext);
int labelWidth = (int) labelBounds.getWidth();
int labelHeight = (int) labelBounds.getHeight();
The bounds is needed to determine if the mouse is hovering over the text when the click occurs.
Next, you must maintain the bounds for each menu item.
Right now, you're only maintaining the names in string and optionStrings. You'll need to maintain the x, y, width and height for every menu item.
Since every menu item each has their own name, size and position, it would be easier to create a new type composed of these properties:
public class Label {
private String name;
private Font font;
private int x, y;
private int width, height;
public Label(String name, Font font, int x, int y, int width, int height) {
this.name = name;
this.font = font;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
}
Instead of a String[], you could have a Label[]. Although it's preferrable to use List for it's higher-level functionality:
public class InGameMenu implements MouseListener {
private List<Label> labels;
public void mousePressed(MouseEvent event) {
int x = event.getX();
int y = event.getY();
labels.forEach(label -> {
// if (mouse hovering over label)
// ...
});
}
}
Then, you must determine if the mouse position is within the label's position.
The formula is pretty simple:
// checks if mouse intersects with label on X axis
intersectsHorizontally := mouseX > labelX && mouseX < labelX + labelWidth;
// checks if mouse intersects with label on Y axis
intersectsVertically := mouseY > labelY && mouseY < labelY + labelHeight;
// if both conditions above are true, mouse is hovering over label
The easiest way to implement this would be to give your Label objects a containsPoint(int x, int y) behavior:
public class Label {
private int x, y;
private int width, height;
//...
public boolean containsPoint(int pointX, int pointY) {
boolean containsHorizontally = pointX > x && pointX < x + width;
boolean containsVertically = pointY > y && pointY < y + height;
return containsHorizontally && containsVertically;
}
}
Now you can easily determine whether the mouse is hovering over a specific label:
public void mousePressed(MouseEvent event) {
int x = event.getX();
int y = event.getY();
labels.forEach(label -> {
if(label.containsPoint(x, y)) {
//...
}
});
}
Finally, you must determine which label was clicked
There are many ways to go about this. Instead of a List, you could maintain the labels independently and check each one:
public class InGameMenu implements MouseListener {
private Label startLabel;
private Label loadLabel;
public InGameMenu(Label startLabel, Label loadLabel) {
this.startLabel = startLabel;
this.loadLabel = loadLabel;
}
public void mousePressed(MouseEvent event) {
int x = event.getX();
int y = event.getY();
if(startLabel.containsPoint(x, y)) {
//start game
} else if(loadLabel.containsPoint(x, y)) {
//load game
}
}
}
But this isn't dynamic, as InGameMenu is forced to know about every label it has, and adding labels would be a pain.
A better approach is to create all the Label objects outside of InGameMenu, as the examples above have been doing:
FontRenderContext renderContext = ...;
Font font = ...;
// start label
String labelText = "Start";
Rectangle2D labelBounds = font.getStringBounds(labelText, renderContext);
int x = ...;
int y = ...;
int width = (int) labelBounds.getWidth();
int height = (int) labelBounds.getHeight();
Label label = new Label(labelText, font, labelX, labelY, labelWidth, labelHeight);
// list of all labels for menu
List<Label> labels = new ArrayList<>();
labels.add(label);
InGameMenu menu = new InGameMenu(labels);
Then have the label object tell us when it has been clicked. We can do this by giving Label a click() method for InGameMenu to trigger when the label has been clicked:
public class InGameMenu implements MouseListener {
private List<Label> labels;
public InGameMenu(List<Label> labels) {
this.labels = labels;
}
public void mousePressed(MouseEvent event) {
int x = event.getX();
int y = event.getY();
labels.forEach(label -> {
if(label.containsPoint(x, y))
label.click();
});
}
}
Then allowing Label to accept a callback function:
public class Label {
private String name;
private Font font;
private int x, y;
private int width, height;
private Runnable onClick;
public Label(String name, Font font, int x, int y, int width, int height, Runnable onClick) {
this.name = name;
this.font = font;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.onClick = onClick;
}
public void click() {
onClick.run();
}
//...
}
So when you create Label objects, you can give them actions that make use of outside data (maybe a GameStateManager or something):
Runnable onClick = () -> System.out.println("Start button was clicked!");
Label label = new Label("Start", ..., onClick);
I am creating a pacman game in Java and I have 1 problem I can't get rid of.. The problem is as follows:
I have 4 buttons in the game screen for each way: up, down, left, right. The problem is that I can not use the buttons in the same time in x position as in the y position because I always get a value of 0 ( you understand this if you see my code below )
Below you can find my class for the pacman that i think is related to the problem ( see setBesturing(int besturing) method )
package h04PacMan;
/*
* PacMan laten bewegen
*/
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class PacMan extends JPanel implements ActionListener {
private int horizontalePlaats = 250; // x location
private int verticalePlaats = 150; // y location
private int richtingEnSnelheid = +10; // speed
private final int WACHTTIJD = 500; // DELAY
int diameter;
int waarde;
public PacMan() {
// create timer and start timer
javax.swing.Timer autoKlik = new javax.swing.Timer(WACHTTIJD, this);
autoKlik.start();
}
public int getHorPlaats() {
return horizontalePlaats;
}
// current y positie
public int getVerPlaats() {
return verticalePlaats;
}
// speed and way;
public int getRichtingEnSnelheid() {
return richtingEnSnelheid;
}
// new x position
public void setHorPlaats(int nieuweHorPlaats) {
if(nieuweHorPlaats > getWidth()) {
nieuweHorPlaats = 0;
}
else if(nieuweHorPlaats < 0) {
nieuweHorPlaats = getWidth();
}
horizontalePlaats = nieuweHorPlaats;
}
// new y position
public void setVerPlaats(int nieuweVerPlaats) {
if(nieuweVerPlaats > getHeight()) {
nieuweVerPlaats = 0;
}
else if(nieuweVerPlaats < 0) {
nieuweVerPlaats = getHeight();
}
verticalePlaats = nieuweVerPlaats;
}
public void setRichtingEnSnelheid(int nieuweRichtingEnSnelheid) {
richtingEnSnelheid = nieuweRichtingEnSnelheid;
}
//movement
public void setBesturing(int besturing) {
besturing = waarde;
if(waarde == 0) {
setVerPlaats( getVerPlaats() + richtingEnSnelheid);
}
else if(waarde == 1){
setHorPlaats( getHorPlaats() + richtingEnSnelheid);
}
}
#Override
public void actionPerformed(ActionEvent e) {
setBesturing(waarde);
System.out.println(waarde);
repaint();
}
DrawPacMan pacman = new DrawPacMan();
DrawPacMan ghost1 = new DrawPacMan();
DrawPacMan ghost2 = new DrawPacMan();
public void paintComponent(Graphics g) {
super.paintComponent(g);
// pacman movement
diameter = 75;
pacman.drawPacMan(g, getHorPlaats(), getVerPlaats(), diameter, Color.yellow);
// ghosts movement
int g1x;
for(g1x = 0; g1x < 10; g1x++) {
pacman.drawGhost(g, g1x, 40, diameter, Color.red);
}
pacman.drawGhost(g, 170, 70, diameter, Color.blue);
}
}
and this is the actionListener in my gamecontrol class
#Override
public void actionPerformed(ActionEvent e) {
if(e.getSource() == links) {
pacman.setRichtingEnSnelheid( -10 );
pacman.setBesturing(1);
System.out.println("links");
}
else if(e.getSource() == rechts) {
pacman.setRichtingEnSnelheid( +10 );
pacman.setBesturing(1);
System.out.println("rechts");
}
else if(e.getSource() == boven) {
pacman.setRichtingEnSnelheid( -10);
pacman.setBesturing(0);
System.out.println("boven");
}
else {
pacman.setRichtingEnSnelheid( +10);
pacman.setBesturing(0);
System.out.println("beneden");
}
}
Here is your problem:
//movement
public void setBesturing(int besturing) {
besturing = waarde; <-- THIS LINE
if(waarde == 0) {
setVerPlaats( getVerPlaats() + richtingEnSnelheid);
}
else if(waarde == 1){
setHorPlaats( getHorPlaats() + richtingEnSnelheid);
}
}
You are overwriting the value of besturing with the old value waarde. It should be the other way around.
//movement
public void setBesturing(int besturing) {
waarde = besturing; <-- THIS LINE
if(waarde == 0) {
setVerPlaats( getVerPlaats() + richtingEnSnelheid);
}
else if(waarde == 1){
setHorPlaats( getHorPlaats() + richtingEnSnelheid);
}
}
I think inside the method setBesturing(int besturing) should be:
waarde = besturing;
In your setBesturing method: -
besturing = waarde;
This line never changes the value of your waarde.. It is always zero.
You should do the assignment the other way : -
this.waarde = besturing;
You'll need to keep track of whether the button is pressed or not indepentently. Also, you're model doesn't support a diagonal movement at all (I'm not sure what your nederlands names exactly mean, i can interprete snelheid=Schnelligkeit=speed and some more).
You'll need to put diagonal movement into the model (e.g. give him an x and y velocity indepent from each other)
In android, I can do like that where user can click outside of editview to hide the virtual keyboard.
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
View v = getCurrentFocus();
boolean ret = super.dispatchTouchEvent(event);
if (v instanceof EditText) {
View w = getCurrentFocus();
int scrcoords[] = new int[2];
w.getLocationOnScreen(scrcoords);
float x = event.getRawX() + w.getLeft() - scrcoords[0];
float y = event.getRawY() + w.getTop() - scrcoords[1];
if (event.getAction() == MotionEvent.ACTION_UP
&& (x < w.getLeft() || x >= w.getRight() || y < w.getTop() || y > w
.getBottom())) {
InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(getWindow().getCurrentFocus()
.getWindowToken(), 0);
}
}
return ret;
}
What about in blackberry? I want to run for VirtualKeyboard.isSupported() only.
Update
public class Custom_EditField extends EditField {
private int width, row, color;
private MainScreen mainscreen;
Custom_EditField(long style, int width, int row, MainScreen mainscreen) {
super(style);
this.width = width;
this.row = row;
this.mainscreen = mainscreen;
}
public int getPreferredHeight() {
return Font.getDefault().getHeight() * row;
}
public int getPreferredWidth() {
return width;
}
protected void onFocus(int direction) {
if (VirtualKeyboard.isSupported())
mainscreen.getVirtualKeyboard().setVisibility(
VirtualKeyboard.SHOW_FORCE);
invalidate();
super.onFocus(direction);
}
protected void onUnfocus() {
if (VirtualKeyboard.isSupported())
mainscreen.getVirtualKeyboard().setVisibility(
VirtualKeyboard.HIDE_FORCE);
invalidate();
super.onUnfocus();
}
public boolean isFocusable() {
return true;
}
protected void layout(int maxWidth, int maxHeight) {
super.layout(maxWidth,
Math.min(maxHeight, Font.getDefault().getHeight() * row));
super.setExtent(maxWidth,
Math.min(maxHeight, Font.getDefault().getHeight() * row));
}
protected void paint(Graphics graphics) {
int rectHeight = getPreferredHeight();
int rectWidth = getPreferredWidth();
try {
color = Color.BLACK;
graphics.setColor(color);
graphics.drawRect(0, 0, rectWidth, rectHeight);
super.paint(graphics);
} finally {
graphics.setColor(color);
}
}
}
This editfield will hide the keypad if you click on another field but not anypoint.
I have this utility code for showing, or hiding the keyboard. This should be valid for OS 4.7 and above. Let me know if you need to support lower OS versions.
/** Hides the virtual keyboard, if there is one showing. */
public static void hideKeyboard() {
VirtualKeyboard kb = UiApplication.getUiApplication().getActiveScreen().getVirtualKeyboard();
if (kb != null) {
kb.setVisibility(VirtualKeyboard.HIDE);
}
}
/** #return TRUE if the virtual keyboard is hidden, or not supported */
public static boolean isKeyboardHidden() {
if (VirtualKeyboard.isSupported()) {
VirtualKeyboard kb = UiApplication.getUiApplication().getActiveScreen().getVirtualKeyboard();
if (kb != null) {
int visibility = kb.getVisibility();
return ((visibility == VirtualKeyboard.HIDE)
|| (visibility == VirtualKeyboard.HIDE_FORCE));
}
}
return true;
}
Note that I made these static functions. So, if you put them in a class named UiUtilities, then you would call them like:
if (!UiUtilities.isKeyboardHidden()) {
UiUtilities.hideKeyboard();
}
As far as where to trigger this code, here's what I recommend, instead of overriding onUnfocus(). I'm not sure this is the easiest, or most efficient way to solve the problem (so I welcome other answers!), but I think this will work.
I told you a couple answers ago that you normally should not override the touchEvent() method in your code. For things like normal buttons, I think that's true. This might be one example where you need to. You should have a Manager (or VerticalFielManager, or similar) that represents the screen that this EditField is on. In that manager, implement the touchEvent() method like this:
import net.rim.device.api.ui.TouchEvent;
protected boolean touchEvent(TouchEvent event) {
// We take action when the user completes a click (a.k.a. unclick)
int eventCode = event.getEvent();
if ((eventCode == TouchEvent.UNCLICK) || (eventCode == TouchEvent.DOWN)) {
// Get the touch location, within this Manager
int x = event.getX(1);
int y = event.getY(1);
if ((x >= 0) && (y >= 0) && (x < getWidth()) && (y < getHeight())) {
int field = getFieldAtLocation(x, y);
if (field >= 0) {
// Let event propagate to child field
return super.touchEvent(event);
} else {
if (eventCode == TouchEvent.UNCLICK) {
// A completed click anywhere else in this manager should dismiss the keyboard
UiUtilities.hideKeyboard();
} else {
// This is just a soft touch (TouchEvent.DOWN), without full click
setFocus();
}
// Consume the event
return true;
}
}
}
// Event wasn't for us, let superclass handle in default manner
return super.touchEvent(event);
}
Try that. You might need to change my logic, depending on whether you want to hide the keyboard for a full click, versus a simple touch down (if you're new to BlackBerry, it might not be clear what the difference between those are). But, I think this should get you close(r).