# Swing GUI application to change colour of circle with JSlider

The below code works as intended and I'm generally happy with it - the sliders each change an RGB value to modify the color of the circle in circlePanel. Likewise the diameter slider changes the size.

Could the code be refactored/restructured to make it better/more readable? The CircleSlider constructor is quite long - I'm wondering could some of it be removed to separate methods/classes.

CircleSlider.java

//import statements
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;

//CircleSlider class to setup GUI and main logic
public class CircleSlider extends JFrame{
//instantiate GUI components
private final JPanel circlePanel;
private final JSlider diameterSlider, redSlider, greenSlider, blueSlider;
private final JLabel diameterLabel, redLabel, greenLabel, blueLabel;
private final JTextArea displayArea;
private final GridBagLayout layout;
private final GridBagConstraints constraints;

//circle properties
private int diameter = 75;

private Color circleColor;

//constructor to set up GUI
public CircleSlider(){
//call to JFrame constructor to set title
super("Circle Slider");

//create new layout
layout = new GridBagLayout();
constraints = new GridBagConstraints();
setLayout(layout);

displayArea = new JTextArea(6,10);
displayArea.setText("Adjust the diameter slider to see information on the circle");
addComponent(displayArea, 14, 0, 20, 8, 1, 1,
GridBagConstraints.BOTH, GridBagConstraints.CENTER);

//add JPanel to house the circle
circlePanel = new JPanel(){
@Override
protected void paintComponent(Graphics g){
super.paintComponent(g);

//set color of drawn graphics
g.setColor(circleColor);
//Draw circle in centre of circlePanel
g.fillOval((circlePanel.getWidth() - diameter) / 2, (circlePanel.getHeight() - diameter) / 2,
diameter, diameter);
}
};
addComponent(circlePanel, 1, 3, 16, 11, 1, 1,
GridBagConstraints.BOTH, GridBagConstraints.CENTER);

diameterLabel = new JLabel("Diameter");
addComponent(diameterLabel, 1, 1, 1, 1, 0, 1,
GridBagConstraints.NONE, GridBagConstraints.WEST);

//add the slider to control diameter of circle
diameterSlider = new JSlider(0, 150, 50);
//add listener as anonymous inner class
new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
double radius = diameter / 2.0;
setDiameter(diameterSlider.getValue());
String message = String.format(
"Diameter: %d%nArea: %.2f%nCircumference: %.2f",
displayArea.setText(message);
}
}
);
addComponent(diameterSlider, 2, 1, 1, 1, 0, 1, GridBagConstraints.NONE,
GridBagConstraints.WEST);

//create listener for color sliders
SliderListener sliderListener = new SliderListener();

redLabel = new JLabel("RGB: Red");
addComponent(redLabel, 4, 1, 1, 1, 0, 1, GridBagConstraints.NONE,
GridBagConstraints.WEST);

redSlider = new JSlider(0, 255, 0);
addComponent(redSlider, 5, 1, 1, 1, 0, 1, GridBagConstraints.NONE,
GridBagConstraints.WEST);

greenLabel = new JLabel("RGB: Green");
addComponent(greenLabel, 7, 1, 1, 1, 0, 1, GridBagConstraints.NONE,
GridBagConstraints.WEST);

greenSlider = new JSlider(0, 255, 0);
addComponent(greenSlider, 8, 1, 1, 1, 0, 1, GridBagConstraints.NONE,
GridBagConstraints.WEST);

blueLabel = new JLabel("RGB: Blue");
addComponent(blueLabel, 11, 1, 1, 1, 0, 1, GridBagConstraints.NONE,
GridBagConstraints.WEST);

blueSlider = new JSlider(0, 255, 0);
addComponent(blueSlider, 12, 1, 1, 1, 0, 1, GridBagConstraints.NONE,
GridBagConstraints.WEST);
}

//ChangeListener for color sliders - implemented as private inner class as
//all color sliders perform same method, setColor()
class SliderListener implements ChangeListener{
@Override
public void stateChanged(ChangeEvent e){
setColor(redSlider.getValue(), greenSlider.getValue(), blueSlider.getValue());
}
}

//helper method to add a component to JPanel or JFrame
private void addComponent(Component component, int row, int column, int width, int height,
int weightx, int weighty, int fillConstraint, int anchorConstraint) {
constraints.gridx = column;
constraints.gridy = row;
constraints.gridwidth = width;
constraints.gridheight = height;
constraints.weightx = weightx;
constraints.weighty = weighty;
constraints.fill = fillConstraint;
constraints.anchor = anchorConstraint;
layout.setConstraints(component, constraints);
}

//set the diameter of the circle
private void setDiameter(int newDiameter){
//if new diameter is negative, set to 10
diameter = newDiameter >= 0 ? newDiameter : 10;
repaint();
}

//set the color of the circle
private void setColor(int newRed, int newGreen, int newBlue){
circleColor = new Color(
//if any given color is negative, set to 10
newRed >= 0 ? newRed : 10,
newGreen >= 0 ? newGreen : 10,
newBlue >= 0 ? newBlue : 10);
getGraphics().setColor(circleColor);
repaint();
}

//Find the area of a circle given the radius
}

//find circumference of circle given the radius
return 2 * Math.PI * radius;
}
}


CircleSliderTest.java

import javax.swing.*;

public class CircleSliderTest {
public static void main(String[] args){
CircleSlider app = new CircleSlider();

app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
app.setSize(600,400);
app.setVisible(true);
}
}


1) Is your CircleSlider really a window? Your code says yes, but as a user, I would find that quite strange. Your CircleSlider should use JFrame as an instance. You can see that clearly in your main method. You are using three methods from it. What about the rest? JFrame has probably 30 methods or more. Can I call all of them without destroying your CircleSlider class? Do I have to call the methods you provided or can I leave them untouched? Should I be able to call them?
- Probably not. Define your own methods.

2) I guess you are writing this because you feel like it would waste too many lines otherwise, right?

private final JSlider diameterSlider, redSlider, greenSlider, blueSlider;


The problem is not the waste of lines, but the number of instance variables you need for your class. That's your state and your state consists of 14 variables.
Personally, I would try to stay below 5. It makes reasoning much easier, it makes it more testable, it keeps your class smaller and you could probably initialize the full state through the constructor - to name a few advantages.

3)

//set the diameter of the circle
private void setDiameter(int newDiameter){
//if new diameter is negative, set to 10
diameter = newDiameter >= 0 ? newDiameter : 10;
repaint();
}


If you already write something like "This method does X with Y" then the method should be a member of Y and Y should be a class. In your case, make a circle class and put the methods with these comments inside it.

4) Yes, your constructor is really long. It seems like your application has different parts, but all of them are inside your CircleSlider class. You should create new classes for these parts, for example, a class containing the sliders for your circle (CircleAdjustment could be a name for it).

5)

addComponent(greenLabel, 7, 1, 1, 1, 0, 1, GridBagConstraints.NONE, GridBagConstraints.WEST);


It's good that you are trying to help yourself with additional methods, but this one is quite difficult to read. I could easily swap some numbers and without looking at your method I wouldn't even dare to guess what these numbers mean. You could use additional classes to get around this:

    // instead of:
constraints.gridx = column;
constraints.gridy = row;
constraints.gridwidth = width;
constraints.gridheight = height;
// something like this:
new Grid(column, row, width, height)


6) All these numbers you are using... ask yourself, do they depend on each other? I am sure some of them do because you are a grid-based layout and if you enlarge one field, it would influence another. This should be visible in your code. The easiest approach (not best) would be to make constants at the beginning of the class which depends on each other:

private static final int RED_SLIDER_Y = 1;
private static final int GREEN_SLIDER_Y = RED_SLIDER_Y +1;
// ...


## EDIT:

Here is an example of how an Adjustment class and some others around it could look like. I didn't implement the layout stuff completely, but the rest should be ok. If you've further questions, I am here.

/**
* Offers a convenient way to initialize a slider. Note that it is final like
* the other classes. If you don't design your class to be inherited from, make
* it final. Careless inheritance is dangerous and leads to bad code and bugs.
*/
public final class SliderValues {
private final int min;
private final int max;
private final int value;

/**
* Secondary constructor. It's purpose is to offer another way to construct
* the object. It's important that every secondary constructor has
* only a single statement consisting of "this(...)" which calls the
* primary constructor. Only the primary constructor sets the fields.
*/
public SliderValues(int min, int max) {
this(min, max, 0);
}

/**
* Primary constructor. Ideally this is how a primary constructor should
* look like: Just a simple setup without methods or calculations.
*/
public SliderValues(int min, int max, int value) {
this.min = min;
this.max = max;
this.value = value;
}

/**
* Yes, you could also have just three getters, but I prefer this option,
* because the purpose of this class is to initialize a slider and that's
* what this method does.
* This class is not a data container.
*/
public JSlider initialized(JSlider slider) {
slider.setMinimum(min);
slider.setMaximum(max);
slider.setValue(value);
return slider;
}
}

public final class LabeledSlider {
private final JPanel panel;
private final JSlider slider;
private final JLabel label;

/**
* A secondary constructor again for the sake of convenience.
*/
public LabeledSlider(String text, int min, int max, ChangeListener listener) {
this(
text,
new SliderValues(min, max),
listener
);
}

/**
* Why didn't I just use a constructor with five values instead of the
* SliderValues class? First, three integers in a row are risky for the
* client. He could mistaken the order. Additionally, I don't want more than
* 4 parameters per method/constructor.
* @param listener I replaced the ChangeListener for the Consumer. The advantage
*  is that the caller doesn't need to somehow get the value of the slider
*  but instead he just gets it from the slider. Additionally, the slider
*  can hold its encapsulation and doesn't need to provide a getValue()
*  method.
*/
public LabeledSlider(String text, SliderValues values, Consumer<Integer> listener) {
label = new JLabel(text);
slider = values.initialized(new JSlider());
e -> listener.accept(slider.getValue())
);
panel = new JPanel(new BorderLayout());
}

/**
* Note that I am not inheriting from any class, because there is exactly
* one thing I want this class to do: Being attached on a container.
* Inheriting from Container would give me this possibility - and 30 other
* methods I don't want. This is a neat alternative using only one method.
*/
}
}

private final JPanel panel;

panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
new LabeledSlider(
"Diameter",
new SliderValues(0, 150, 50),
/*
I don't know if you've used the other syntax on purpose, but if not,
this is a way to shorten your ChangeListener creation. It's called
"lambda" and is usable on Interfaces with only one method
(ChangeListener is such an Interface).

Note that this isn't the best application for a lambda. Normally
you use it if you have short code around 4 lines or less, because
it tends to hurt readability if it's longer.
*/
e -> {
double radius = diameter / 2.0;
setDiameter(diameterSlider.getValue());
String message = String.format(
"Diameter: %d%nArea: %.2f%nCircumference: %.2f",
displayArea.setText(message);
}
new LabeledSlider(
"RGB: Red",
0,
255,
value -> circle.red(value)
new LabeledSlider(
"RGB: Green",
0,
255,
circle::green // alternative syntax for the lambda called "method reference".
// If you don't understand it, just use the normal one.

// It works only if your lambda provides the same amount and types
// of values as the the method you're referencing takes.
// In this case, green takes one argument (integer) and you get
// one integer from the lambda.