Reputation: 141
I am currently making a volume slider for my game. I want to have a toggleable UI where the player can set their preferred volume, then resume playing.
Disclaimer: This is from a group project where everybody is a first timer to making a more complex game, and we're mainly using this project to gain experience. I'm sure this is full of bad practice and inefficiency, but at the end of the day we're making this mostly for fun.
Here's what it should look like:
Here's the minimal reproducible example in 3 classes:
Main
Intermediate
Slider
Main
import javax.swing.*;
import java.awt.*;
public class Main
{
public static void main(String[] args)
{
JFrame frame = new JFrame();
Intermediate inte = new Intermediate(frame);
frame.add(inte);
frame.setPreferredSize(new Dimension(900,600));
frame.pack();
frame.setVisible(true);
}
}
Intermediate
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
public class Intermediate extends JPanel
{
boolean b = false;
private Timer tim;
private JFrame f;
private Slider s;
public Intermediate(JFrame f)
{
super();
this.f=f;
tim = new Timer(10, new NewFrameListener());
tim.start();
}
public void stop()
{
tim.stop();
}
public void restart()
{
tim.start();
}
public void handleInputs()
{
this.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), "pressed left");
this.getActionMap().put("pressed left", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("left");
}
});
this.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_U, 0, false), "pressed u");
this.getActionMap().put("pressed u", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
if(b)
{
restart();
b=false;
}
else
{
s = new Slider();
f.getContentPane().add(s);
f.setVisible(true);
stop();
b=true;
}
}
});
}
@Override
protected void paintComponent(Graphics g)
{
g.setColor(Color.gray);
super.paintComponent(g);
g.fillRect(0,0,900,600);
}
class NewFrameListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent ae)
{
// System.out.println("woo");
handleInputs();
repaint();
}
}
}
Slider
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
public class Slider extends JPanel implements ChangeListener {
public Slider()
{
JPanel container = new JPanel();
JSlider slider = new JSlider(JSlider.HORIZONTAL, 0, 10, 5);
slider.addChangeListener(this);
slider.setMajorTickSpacing(10);
slider.setMinorTickSpacing(1);
slider.setPaintTicks(true);
slider.setPaintLabels(true);
slider.setSize(200, 50);
slider.setBounds(200,200, 200, 50);
slider.setEnabled(true);
container.setBackground(Color.GRAY);
container.add(slider);
this.add(container);
}
@Override
public void stateChanged(ChangeEvent e) {
JSlider source = (JSlider) e.getSource();
if (!source.getValueIsAdjusting()) {
System.out.println(source.getValue());
}
}
}
The JPanel called container in Slider is completely optional, the functionality remains the same, but then the Slider would fill the whole frame.
The expected behaviour is that the visibility of the frame that contains the slider is toggled every time 'u' is pressed. And that does work, until the slider is interacted with. When that happens every input seems to be suppressed and the frame can no longer be hidden. No errors or exceptions thrown when this happens either.
What is causing this behaviour? How can it fixed?
Edit: Added the relevant code of newFrameTimer and NewFrameListener, as well as repaint and paintComponent.
Edit2: I have now tried placing the Slider's JPanel in an other JPanel to be able to set its size, and I can now interact with the outer JPanel without the action listener ceasing to function, but the moment I touch the Slider it breaks again.
Furthermore I tried disabling the change listener portion of the slider, but that still doesn't fix the issue.
Edit3: I have now also recreated this behaviour independently of the game, and the same thing happens. An input causes the slider (or a JPanel containing the slider) to get added to the frame, at this point the the slider can be toggled, using keyboard inputs, but as soon as the slider is interacted with all keyboard input to the outer Panel is lost. I'll try to see if pressing a button or something similar can restore the expected functionality.
Upvotes: 0
Views: 293
Reputation: 141
The problem is now resolved, thanks to everyone who commented, special thanks to @macrofox @Abra and @Andrew Thompson .
The cause of the issue was that the when the Slider is in focus Intermediate's listeners are basically disabled, so to return to the game I have to disable the Slider's visibility in a KeyListener of its own.
Side note: In order to resume the game in one press of 'u' the input map in Intermediate for 'u' has to be on onKeyRelease = true
here are the 3 classes in their properly working states:
Main
(unchanged)
import javax.swing.*;
import java.awt.*;
public class Main
{
public static void main(String[] args)
{
JFrame frame = new JFrame();
Intermediate inte = new Intermediate(frame);
frame.add(inte);
frame.setPreferredSize(new Dimension(900,600));
frame.pack();
frame.setVisible(true);
}
}
Intermediate
onKeyRelease = true for "u"'s input map
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class Intermediate extends JPanel
{
boolean b = false;
private Timer tim;
private JFrame f;
private Slider s;
public Intermediate(JFrame f)
{
super();
this.f=f;
tim = new Timer(10, new NewFrameListener());
tim.start();
}
public void stop()
{
tim.stop();
}
public void restart()
{
tim.start();
}
public void handleInputs()
{
this.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), "pressed left");
this.getActionMap().put("pressed left", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("left");
}
});
this.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_U, 0, true), "pressed u");
this.getActionMap().put("pressed u", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
if(b)
{
restart();
b=false;
remove(s);
}
else
{
s = new Slider();
f.getContentPane().add(s);
f.setVisible(true);
stop();
b=true;
}
}
});
}
@Override
protected void paintComponent(Graphics g)
{
g.setColor(Color.gray);
super.paintComponent(g);
g.fillRect(0,0,900,600);
}
class NewFrameListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent ae)
{
// System.out.println("woo");
handleInputs();
repaint();
}
}
}
Slider
the slider itself has the whole class Slider added to it as a key listener
now also implements KeyListener, if the key pressed is 'u', visibility of the JPanel is disabled
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class Slider extends JPanel implements ChangeListener, KeyListener {
public Slider()
{
JPanel container = new JPanel();
JSlider slider = new JSlider(JSlider.HORIZONTAL, 0, 10, 5);
slider.addChangeListener(this);
slider.setMajorTickSpacing(10);
slider.setMinorTickSpacing(1);
slider.setPaintTicks(true);
slider.setPaintLabels(true);
slider.setSize(200, 50);
slider.setBounds(200,200, 200, 50);
slider.setEnabled(true);
//container.setBackground(Color.GRAY);
//container.add(slider);
slider.addKeyListener(this);
this.add(slider);
}
@Override
public void stateChanged(ChangeEvent e) {
JSlider source = (JSlider) e.getSource();
if (!source.getValueIsAdjusting()) {
System.out.println(source.getValue());
}
}
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode()==KeyEvent.VK_U){this.setVisible(false);}
}
@Override
public void keyReleased(KeyEvent e) {
}
}
Upvotes: 2