Creating a volume slider for VLCJ audio - java

I am trying to create an volume level bar where different level's of volume are represented with an different color here is my approach
I have 2 arrays
Color[] scales gives different color representation for each volume level if the length of this array is lets say 4 then there are 4 volume level's and so on
float[] weights denotes how much percentage/space each color should occupy in the bar
For example
private final Color scales[]={Color.GREEN,Color.YELLOW,Color.RED};
private final float weights[]={0.3f,0.2f,0.5f};
means there are 3 levels of volume where
if the current volume is <=30% of max then 30% of the volume bar is covered in GREEN
if the current volume is >30% and <= (30+20)=50% the next 20% of the volume bar is covered in YELLOW
if the current volume is >50% and <= (50+50)=100% the final 50% of the volume bar is covered in RED
Now the user interacts with the volume bar by clicking & dragging the mouse hence let' say if the volume bar has dimensions (x=120,y=50) and lets say I click or drag till Xposition=25 then
30% of 120=36
XPosition=25
25<36 hence must draw an green color rect of dimensions x=0,y=0,width=36-25=12,height=50
and we continue likewise for the remaining positions calculating where the user clicks and drawing different color rectangles till that point.
Now I think I butchered the explanation but I am not asking for the code I already have it implemented here
final class VolumeBar extends JPanel
{
VolumeBar()
{
super(new BorderLayout());
add(Box.createRigidArea(new Dimension(500,100)),BorderLayout.NORTH);
add(Box.createRigidArea(new Dimension(500,100)),BorderLayout.SOUTH);
JPanel container=new JPanel(new BorderLayout());
container.add(Box.createRigidArea(new Dimension(120,50)),BorderLayout.WEST);
container.add(Box.createRigidArea(new Dimension(120,50)),BorderLayout.EAST);
container.add(new JVolume(),BorderLayout.CENTER);
add(container,BorderLayout.CENTER);
}
private final class JVolume extends JLabel
{
private final Color scales[]={Color.GREEN,Color.YELLOW,Color.RED};
private final float weights[]={0.6f,0.2f,0.2f};
private int endingX;
private JVolume()
{
addMouseMotionListener(new Drag());
addMouseListener(new Click());
setPreferredSize(new Dimension(260,50));
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2d=(Graphics2D)g;
Dimension size=getSize();
float endPoints[]=new float[scales.length+1];
endPoints[0]=0;
for(int i=1;i<endPoints.length;i++){endPoints[i]=endPoints[i-1]+(size.width*weights[i-1]);}
for(int i=1;i<endPoints.length;i++)
{
float
prev=endPoints[i-1],
current=endPoints[i];
if(endingX>prev)
{
g2d.setColor(scales[i-1]);
g2d.fill(new Rectangle2D.Float(prev,0,endingX>current?current-prev:endingX-prev,size.height));
}
else{break;}
}
g2d.setColor(getBackground());
Polygon clear=new Polygon();
clear.addPoint(0,0);
clear.addPoint(size.width,0);
clear.addPoint(0,size.height);
clear.addPoint(0,0);
g2d.fill(clear); //clear the upper left triangle with background to make it look like an increasing triangle
g2d.setColor(Color.BLACK); draw the lower right triangle to give the bar some border
Polygon polygon=new Polygon();
polygon.addPoint(1,size.height-1);
polygon.addPoint(size.width-1,1);
polygon.addPoint(size.width-1,size.height-1);
polygon.addPoint(1,size.height-1);
g2d.drawPolygon(polygon);
}
private void compute(MouseEvent m)
{
endingX=m.getX();
repaint();
}
private final class Drag extends MouseMotionAdapter
{
#Override
public void mouseDragged(MouseEvent m){compute(m);}
}
private final class Click extends MouseAdapter
{
#Override
public void mouseClicked(MouseEvent m){compute(m);}
}
}
}
And here is how it look's like just click or drag anywhere on the bar
Look's great for the most part but what I am aiming for is the volume bar in VLC media player
Forgive the blurriness, I had to scale the image up but if you look closely you can notice how the color's blend at the boundary for example there is an gradient from green->yellow making some white in between at the boundary and there is an gradient from yellow->red making some orange in between
I want to achieve this gradient.
Ideas anyone?

As recommended by #AndrewThompson in the comments above you can use MultipleGradientPaint using a LinearGradientPaint.
Currently I'm confused by which of the 2 you need, above you said:
if the current volume is <=30% of max then 30% of the volume bar is covered in GREEN
if the current volume is >30% and <= (30+20)=50% the next 20% of the volume bar is covered in YELLOW
if the current volume is >50% and <= (50+50)=100% the final 50% of the volume bar is covered in RED
And then you said:
I need 60% of my bar in green then 20% in yellow not the other way around which is what LinearGradient requires.you see why it's a problem now?
But those are just numbers.
What you can do is create your LinearGradientPaint this way:
Rectangle2D rect = new Rectangle2D.Double(10, 10, 250, 150);
Point2D startPoint = new Point2D.Double(rect.getMinX(), rect.getCenterY());
Point2D endPoint = new Point2D.Double(rect.getMaxX(), rect.getCenterY());
float[] percentages = new float[] {0.0f, 0.6f, 0.8f};
Color[] colors = new Color[] {Color.GREEN, Color.YELLOW, Color.RED};
LinearGradientPaint gradient = new LinearGradientPaint(startPoint, endPoint, percentages, colors, CycleMethod.REPEAT);
You were confused at where you need to start the gradient because I guess your percentages were: 0.6f, 0.2f, 0.2f but instead you need to define the start point and then add the next percentage from there, 0.0f, 0.6f, 0.8f (This will start at 0% then go up to 60% to green, and then up to 80% to yellow, and then the rest to 100% on red.
And you get this output.
MRE for you to test changes:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.LinearGradientPaint;
import java.awt.RenderingHints;
import java.awt.MultipleGradientPaint.CycleMethod;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class LinearGradientExample {
private JFrame frame;
private JPanel pane;
public static void main(String[] args) {
SwingUtilities.invokeLater(new LinearGradientExample()::createAndShowGUI);
}
#SuppressWarnings("serial")
private void createAndShowGUI() {
frame = new JFrame(getClass().getSimpleName());
Rectangle2D rect = new Rectangle2D.Double(10, 10, 250, 150);
Point2D startPoint = new Point2D.Double(rect.getMinX(), rect.getCenterY());
Point2D endPoint = new Point2D.Double(rect.getMaxX(), rect.getCenterY());
float[] percentages = new float[] {0.0f, 0.6f, 0.8f};
Color[] colors = new Color[] {Color.GREEN, Color.YELLOW, Color.RED};
LinearGradientPaint gradient = new LinearGradientPaint(startPoint, endPoint, percentages, colors, CycleMethod.REPEAT);
pane = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setPaint(gradient);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.fill(rect);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(300, 200);
}
};
frame.add(pane);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
}

Related

Repainting a dirty region interfere with graphics2D clipbounds

In the following reproducer, I am reproducing what I'm seeing in the graphical interface.
I'd like to repaint a dirty region of a canvas (a JPanel), because a full repaint introduces more latencies.
However when the repaint (dirty rectangle) function is invoked, it causes some issues with nested Graphics2D objects. In particular
This code may mis-use clip bounds, but I wasn't expecting this "artifact" upon the creation of the sub (nested) graphic context.
What would be the proper way to deal with clips, nested graphic contexts, and the dirty region (or rectangle) repaint in this situation.
My intuition tells me the code should compute its shape without using the clipping area.
import javax.swing.*;
import java.awt.*;
import java.awt.geom.Rectangle2D;
public class Reproducer {
public static void main(String[] args) {
var comp = new MyJPanel();
var timer = new Timer(2_000, e -> {
SwingUtilities.invokeLater(comp::bug);
});
timer.start();
timer.setRepeats(true);
SwingUtilities.invokeLater(() -> {
var frame = new JFrame("Reproducer");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(new Dimension(300, 300));
frame.setVisible(true);
frame.getContentPane().add(comp);
});
}
private static class MyJPanel extends JPanel {
public MyJPanel() {
setSize(600, 600);
setPreferredSize(getSize());
}
#Override
protected void paintComponent(Graphics g) {
var g2 = (Graphics2D) g;
super.paintComponent(g2);
g2.setColor(Color.cyan);
g2.fill(g2.getClip());
var graphicsBox = (Graphics2D) g2.create(10, 10, 290, 290);
var clipBounds = graphicsBox.getClipBounds();
graphicsBox.setColor(Color.LIGHT_GRAY);
graphicsBox.setStroke(new BasicStroke(2));
graphicsBox.drawRect(clipBounds.x, clipBounds.y, clipBounds.width, clipBounds.height);
graphicsBox.setColor(Color.red);
graphicsBox.fill(new Rectangle2D.Double(clipBounds.x + 2,
clipBounds.y + 2,
clipBounds.width - 2,
clipBounds.height - 2));
graphicsBox.dispose();
var graphicsOverlay = (Graphics2D) g2.create(100, 100, 100, 100);
graphicsOverlay.setColor(Color.YELLOW);
graphicsOverlay.fillOval(10, 10, 60, 60);
graphicsOverlay.dispose();
}
public void bug() {
getVisibleRect();
repaint(100, 100, 100, 100);
}
}
}
EDIT by moving the fill above drawRect
So, there are a couple of "mistakes"
I found this...
graphicsBox.drawRect(clipBounds.x, clipBounds.y, clipBounds.width, clipBounds.height);
graphicsBox.setColor(Color.red);
graphicsBox.fill(new Rectangle2D.Double(clipBounds.x + 2,
clipBounds.y + 2,
clipBounds.width - 2,
clipBounds.height - 2));
to be a little erroneous, which meant when a clip was applied, the background area was not been filled properly.
The call to super.paintComponent was filling the clip area with the background color of the component, but graphicsBox.fill wasn't filling the entire area, allow part of the background color to be seen.
I also found the result of Graphics2D#create(int, int, int, int) to be a little surprising
The new Graphics object has its origin translated to the specified point (x, y). Its clip area is determined by the intersection of the original clip area with the specified rectangle.
(emphasis added by me)
I mean, honestly, I shouldn't be surprised, but I've just never used these features before so it took me a little by surprise.
So, I corrected the graphicsBox rendering code so that the fill will fill the entire clipping area first and THEN the rectangle is drawn (within the bounding rectangle)
var graphicsBox = (Graphics2D) g2.create(10, 10, 290, 290);
var clipBounds = graphicsBox.getClipBounds();
graphicsBox.setColor(Color.red);
graphicsBox.fill(new Rectangle2D.Double(clipBounds.x,
clipBounds.y,
clipBounds.width,
clipBounds.height));
Collections.shuffle(colors);
graphicsBox.setColor(colors.get(0));
graphicsBox.setStroke(new BasicStroke(2));
graphicsBox.drawRect(clipBounds.x + 1, clipBounds.y + 1, clipBounds.width - 2, clipBounds.height - 2);
graphicsBox.dispose();
nb: The reference to colors is just a list of Colors which I randomise during the paint process, so I can "see" what's going on
Another trick might be to change the background color of the component to make it easier to "see" where things are been updated incorrectly.
When you have issues like this, it's really important to reduce the amount of clutter to see if you can narrow down the problem, I took out most of the paint code to figure out all these "little" things which were combining to give you issues.
Runnable example...
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class Main {
public static void main(String[] args) {
var comp = new MyJPanel();
var timer = new Timer(2_000, e -> {
SwingUtilities.invokeLater(comp::bug);
});
timer.start();
timer.setRepeats(true);
SwingUtilities.invokeLater(() -> {
var frame = new JFrame("Reproducer");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(new Dimension(300, 300));
frame.setVisible(true);
frame.getContentPane().add(comp);
});
}
private static class MyJPanel extends JPanel {
private List<Color> colors;
public MyJPanel() {
setSize(600, 600);
setPreferredSize(getSize());
colors = Arrays.asList(new Color[] {
Color.BLACK,
Color.BLUE,
Color.CYAN,
Color.DARK_GRAY,
Color.GRAY,
Color.GREEN,
Color.LIGHT_GRAY,
Color.MAGENTA,
Color.ORANGE,
Color.PINK,
Color.RED,
Color.WHITE,
Color.YELLOW
});
}
#Override
protected void paintComponent(Graphics g) {
var g2 = (Graphics2D) g;
super.paintComponent(g2);
g2.setColor(Color.cyan);
g2.fill(g2.getClip());
var graphicsBox = (Graphics2D) g2.create(10, 10, 290, 290);
var clipBounds = graphicsBox.getClipBounds();
graphicsBox.setColor(Color.red);
graphicsBox.fill(new Rectangle2D.Double(clipBounds.x,
clipBounds.y,
clipBounds.width,
clipBounds.height));
Collections.shuffle(colors);
graphicsBox.setColor(colors.get(0));
graphicsBox.setStroke(new BasicStroke(2));
graphicsBox.drawRect(clipBounds.x + 1, clipBounds.y + 1, clipBounds.width - 2, clipBounds.height - 2);
graphicsBox.dispose();
var graphicsOverlay = (Graphics2D) g2.create(100, 100, 100, 100);
graphicsOverlay.setColor(Color.YELLOW);
graphicsOverlay.fillOval(10, 10, 60, 60);
graphicsOverlay.dispose();
}
public void bug() {
getVisibleRect();
repaint(100, 100, 100, 100);
}
}
}
If you're having "performance" issues, you might consider using a BufferedImage as the primary canvas and update it, then simply paint the image.
Alternatively, it might be time to move over to using BufferStrategy, this is going to get you as close to the "metal" as Java/Swing allows

Adding a drawn (and editable) rectangle to a grid array containing sliders and textFields

Disclaimer: This code is for an assignment. Granted, the question I'm asking doesn't have anything to do with the assignment's requirements (it doesn't specify needing any sort of layout), but I wanted to mention that.
My assignment is to create a GUI displaying a rectangle with it's color able to be changed through three sliders (RGB), as well as three text fields displaying each slider's current value (0-255). I've managed to find a way to set up the sliders and textFields in the positions I want them through an array, but I have no idea how to add the rectangle to a slot on that array (or if I even can). The code I have currently also draws the rectangle as two intersecting lines rather than a solid block. Would that have something to do with my layout?
package ExercisePackage;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.GridLayout;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.event.ChangeListener;
import javax.swing.event.ChangeEvent;
import javax.swing.JFrame;
public class MyColorChooser extends JPanel {
// Holds int values
private int red = 255;
private int green = 255;
private int blue = 255;
// Holds color sliders
private JSlider redSlider;
private JSlider greenSlider;
private JSlider blueSlider;
// Holds slider text numbers
private JTextField redText;
private JTextField greenText;
private JTextField blueText;
// Create rectangle
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(new Color(red, green, blue));
g.fillRect(15, 25, 100, 20);
}
public MyColorChooser()
{
// Create and set layout array for specific cell placement
int rows = 3;
int columns = 3;
JPanel[][] panelHolder = new JPanel[rows][columns];
setLayout(new GridLayout(rows, columns, 5, 5));
// Formula for cell placement
for(int m = 0; m < rows; m++) {
for(int n = 0; n < columns; n++) {
panelHolder[m][n] = new JPanel();
add(panelHolder[m][n]);
}
}
// Create sliders
redSlider = new JSlider(SwingConstants.HORIZONTAL, 0, 255, 0);
redSlider.addChangeListener(new SliderListener());
greenSlider = new JSlider(SwingConstants.HORIZONTAL, 0, 255, 0);
greenSlider.addChangeListener(new SliderListener());
blueSlider = new JSlider(SwingConstants.HORIZONTAL, 0, 255, 0);
blueSlider.addChangeListener(new SliderListener());
// Create text fields
redText = new JTextField("0", 3);
redText.setEditable(false);
greenText = new JTextField("0", 3);
greenText.setEditable(false);
blueText = new JTextField("0", 3);
blueText.setEditable(false);
// Add sliders and text fields
panelHolder[0][0].add(redSlider);
panelHolder[0][1].add(greenSlider);
panelHolder[0][2].add(blueSlider);
panelHolder[1][0].add(redText);
panelHolder[1][1].add(greenText);
panelHolder[1][2].add(blueText);
}
// Inner class to handle event changes when the slider is moved
private class SliderListener implements ChangeListener {
public void stateChanged(ChangeEvent e)
{
// Link color values to slider
red = redSlider.getValue();
green = greenSlider.getValue();
blue = blueSlider.getValue();
// Link text field values to slider
redText.setText(Integer.toString(red));
greenText.setText(Integer.toString(green));
blueText.setText(Integer.toString(blue));
// Link rectangle color to sliders
repaint();
}
}
public static void main(String[] args){
JFrame frame = new JFrame();
frame.setContentPane( new MyColorChooser() );
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
It's not clear why you have the JPanel[][] at all your issue appears to be that you adding a bunch of components to a JPanel, that also paints a color box. So the you want to make a new JPanel that paints the color box, and add that to your component.
The first change is to replace your paintComponent with a JPanel.
JPanel colorPanel = new JPanel(){
// Create rectangle
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(new Color(red, green, blue));
g.fillRect(15, 25, 100, 20);
}
#Override
public Dimension getPreferredSize(){
//should be fixed.
return new Dimension(115, 45);
}
}
Then add that component to your layout in the constructor (I am not a fan of doing so much gui work in the constructor.)
//... continuing of constructor.
panelHolder[1][0].add(redText);
panelHolder[1][1].add(greenText);
panelHolder[1][2].add(blueText);
panelHolder[2][0].add(colorPanel);
}
I have now made a component that draws a rectangle and added it to the original layout. Some issues.
the new components size.
the position of painting the rectangle is possibly not where you want it.
the location in the grid layout is probably off.

How do I find the exact size of my JFrame?

I am currently trying to use paintComponent to draw a square in the middle of my JFrame on top of a JPanel. The size of my frame is 600 x 600, but when I try to draw the square with Xcoordinate = 300 and Ycoordinate = 300 the square is not really even close to the middle of my Frame.
Am I doing something wrong? This is for a school project, so any tips would also be appreciated. Thanks in advance!
Write a complete working program that draws a square in the middle of the
frame.
When the user clicks on a square (left click), it then replaces it with 4
smaller
squares drawn in separate quadrants, each of which is a quarter of the size
of the original
square and has random color. If user clicks (right click) on any square, it
is removed/deleted from the frame.
If user remains inactive for 15 seconds ( (i.e., stops clicking), all squares should start moving away
from the center (any speed is fine, overlaps are fine but not preferred). If squares, hit the edges,
they are deleted again. If user presses 'S', the squares stop moving. The frame is cleared when user
presses 'Delete' key three times in quick succession (within 3 seconds) and you start all over again.
Test your program to draw unique patterns.
My frame class:
import javax.swing.*;
import java.awt.*;
public class SquareFrame extends JFrame {
private final int WIDTH = 600, HEIGHT = 600;
private final Dimension frameSize;
private SquarePanel panel;
public SquareFrame(){
panel = new SquarePanel();
frameSize = new Dimension(WIDTH,HEIGHT);
this.setTitle("Sqaures");
this.setSize(frameSize);
this.getContentPane().add(panel);
this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
this.setVisible(true);
}
public static void main(String[] args){
SquareFrame frame = new SquareFrame();
}
}
My panel class:
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
public class SquarePanel extends JPanel implements MouseListener {
public SquarePanel(){
this.setBackground(Color.BLACK);
this.setFocusable(true);
this.setVisible(true);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Square test = new Square();
g.setColor(Color.WHITE);
g.drawRect(test.x,test.y,test.width,test.height);
}
#Override
public void mouseClicked(MouseEvent e) { }
#Override
public void mousePressed(MouseEvent e) { }
#Override
public void mouseReleased(MouseEvent e) { }
#Override
public void mouseEntered(MouseEvent e) { }
#Override
public void mouseExited(MouseEvent e) { }
}
My square class:
import java.awt.*;
public class Square extends Rectangle {
public Square(){
this.height = 100;
this.width = 100;
this.x = 300;
this.y = 200;
}
}
Write a complete working program that draws a square in the middle of the
frame.
Well, I think that is a terrible requirement. The frame contains a title bar and 4 borders. As the frame shrinks in size you won't be able to paint the square on the title bar.
Custom painting is done on the panel that is added to the frame. So I think you should really be trying to center the panel in the bounds of the panel. You need to clarify the actual requirement.
public class Square extends Rectangle {
Why are you extending Rectangle?
If you need, the x, y, width, height information then you just use the getBounds() method of the Rectangle class to get the information.
However, you don't need the x/y values.
If you want to center the rectangle then you need to calculate the x/y values dynamically as you do your custom painting based on the current size of the panel.
What you need to know is the width/height of the rectangle you want to paint, so really you should be creating a Dimension object. Something like:
Dimension d = new Dimension(100, 100);
Then to center the rectangle in the panel you need to know the width of the panel and the width of the rectangle to calculate the location to do the painting of the rectangle:
int x = (getWidth() - d.getWidth()) / 2;
int y = ...
g.drawRect(x, y, d.width, d.height);

Graphics Context misaligned on first paint

I've been working up an answer for another question and came across a bizare issue that I've not seen before...
Basically, the program uses a AffineTransform to provide translation, scaling and rotating of a Graphics element, simple enough stuff, done a thousand times before
The problem is, when the screen first appears, the output is not where it should be, but once I touch one of the controls (adjust one of the slides) it jumps to the right spot.
Based on the screen shots, the Graphics content seems to be misplaced by the amount of the other controls.
If I remove the controls from the GUI, it appears in the right spot (in the center). If I resize the window, it doesn't fix the issue, it only fixes when one of the sliders triggers repaint on the DrawPane...
I've added diagnostics to the output and the all the values are the same - that is, they print the same values when the program first starts and when I adjust all slider values to their initial values.
If I remove the setRotation and setScale calls from the AffineTransform, it doesn't fix it. If I remove the setTranslation, the square isn't initially painted until the panel is updated (it's painted off screen)
If I use Graphics2D g2d = (Graphics)g; instead of g.create(), same result (and yes, I reset the transform before the paintComponent method exited ;))
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.geom.AffineTransform;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class Parker {
public static void main(String[] args) {
new Parker();
}
public Parker() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new ControlPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class ControlPane extends JPanel {
private JSlider slider; //declare slider
private DrawPane myPanel;
public ControlPane() {
setLayout(new BorderLayout());
myPanel = new DrawPane();
myPanel.setBackground(Color.cyan); //change background color
slider = new JSlider(SwingConstants.VERTICAL, 0, 400, 100);// restrains the slider from scaling square to 0-300 pixels
slider.setMajorTickSpacing(20); //will set tick marks every 10 pixels
slider.setPaintTicks(true); //this actually paints the ticks on the screen
slider.addChangeListener(
new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
myPanel.setScale(slider.getValue()); //Wherever you set the slider, it will pass that value and that will paint on the screen
}
}
);
JSlider rotate = new JSlider(SwingConstants.VERTICAL, 0, 720, 0);
rotate.setMajorTickSpacing(20); //will set tick marks every 10 pixels
rotate.setPaintTicks(true); //this actually paints the ticks on the screen
rotate.addChangeListener(
new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
JSlider slider = (JSlider) e.getSource();
myPanel.setAngle(slider.getValue());
}
}
);
add(slider, BorderLayout.WEST);
add(rotate, BorderLayout.EAST);
add(myPanel);
slider.setValue(0);
slider.setValue(100);
rotate.setValue(0);
}
}
public class DrawPane extends JPanel {
private double scale = 1;
private double angle = 0;
private final int rectWidth = 20;
private final int rectHeight = 20;
#Override
protected void paintComponent(Graphics g)//paints obj on the screen
{
super.paintComponent(g); //prepares graphic object for drawing
int originX = getWidth() / 2;
int originY = getHeight() / 2;
int xOffset = -(rectWidth / 2);
int yOffset = -(rectHeight / 2);
g.setColor(Color.BLACK);
Graphics2D g2d = (Graphics2D) g.create();
AffineTransform at = new AffineTransform();
at.translate(originX, originY);
g2d.setTransform(at);
g2d.scale(scale, scale);
g2d.rotate(Math.toRadians(angle), 0, 0);
g2d.fillRect(xOffset, yOffset, rectWidth, rectHeight);
g2d.dispose();
g.setColor(Color.RED);
g.drawRect(originX + xOffset, originY + yOffset, rectWidth, rectWidth);
}
public void setAngle(double angle) {
this.angle = angle;
repaint();
}
public void setScale(int scale) {
// Scaling is normalized so that 1 = 100%
this.scale = (scale / 100d);
repaint();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
}
Basically, it appears that the Graphics context, for some reason is not been translated properly when it is first painted and I have no idea why...
ps- Test on Java 6 and Java 7 under Windows 7
pps- I've also tried setting the initial scale and rotation to other values before the screen was visible, same result
System Properties
awt.toolkit=sun.awt.windows.WToolkit
file.encoding=UTF-8
file.encoding.pkg=sun.io
file.separator=\
java.awt.graphicsenv=sun.awt.Win32GraphicsEnvironment
java.awt.printerjob=sun.awt.windows.WPrinterJob
java.class.path=C:\DevWork\personal\java\projects\wip\SystemProperties\build\classes
java.class.version=51.0
java.endorsed.dirs=C:\Program Files\Java\jdk1.7.0_51\jre\lib\endorsed
java.ext.dirs=C:\Program Files\Java\jdk1.7.0_51\jre\lib\ext;C:\Windows\Sun\Java\lib\ext
java.home=C:\Program Files\Java\jdk1.7.0_51\jre
java.io.tmpdir=C:\Users\shane\AppData\Local\Temp\
java.runtime.name=Java(TM) SE Runtime Environment
java.runtime.version=1.7.0_51-b13
java.specification.name=Java Platform API Specification
java.specification.vendor=Oracle Corporation
java.specification.version=1.7
java.vendor=Oracle Corporation
java.vendor.url=http://java.oracle.com/
java.vendor.url.bug=http://bugreport.sun.com/bugreport/
java.version=1.7.0_51
java.vm.info=mixed mode
java.vm.name=Java HotSpot(TM) 64-Bit Server VM
java.vm.specification.name=Java Virtual Machine Specification
java.vm.specification.vendor=Oracle Corporation
java.vm.specification.version=1.7
java.vm.vendor=Oracle Corporation
java.vm.version=24.51-b03
os.arch=amd64
os.name=Windows 7
os.version=6.1
path.separator=;
sun.arch.data.model=64
sun.cpu.endian=little
sun.cpu.isalist=amd64
sun.desktop=windows
sun.io.unicode.encoding=UnicodeLittle
sun.java.command=systemproperties.SystemProperties
sun.java.launcher=SUN_STANDARD
sun.jnu.encoding=Cp1252
sun.management.compiler=HotSpot 64-Bit Tiered Compilers
sun.os.patch.level=Service Pack 1
user.country=AU
user.dir=C:\DevWork\personal\java\projects\wip\SystemProperties
user.home=C:\Users\shane
user.language=en
user.name=shane
user.script=
user.timezone=
user.variant=
Updated
If I use...
g2d.translate(originX, originY);
g2d.scale(scale, scale);
g2d.rotate(Math.toRadians(angle), 0, 0);
Instead of the AffineTransform, it works fine. I've noted that it doesn't matter how I use the AffineTransform (translation only, rotation only, scale only) I seem to get the same results
Updated with example image
Example showing resizing frame...
nb The last position shift of the rectangle is a result of the MouseListener mentioned below
However, if I add a MosueListener to the DrawPane (either direclty within the class or externally via the myPanel instance) and call repaint on mouseClicked, it re-aligns correctly :P
Updated
If I translate a Shape, using the following
AffineTransform at = new AffineTransform();
at.translate(originX, originY);
at.scale(scale, scale);
at.rotate(Math.toRadians(angle), 0, 0);
g2d.setTransform(at);
Rectangle2D rect = new Rectangle2D.Double(xOffset, yOffset, rectWidth, rectHeight);
Shape shape = at.createTransformedShape(rect);
System.out.println(rect.getBounds());
System.out.println(shape.getBounds());
The resulting output is in align with expectations, but the (graphics) output is still wrong...
The AffineTransform associated with the graphics context passed to paintComponent() is not always the identity transform. For reasons that aren't clear, the m12 entry has the value 38.0 initially and after resizing the enclosing frame. Trivially, one can modify the copy supplied by g.create().
Graphics2D g2d = (Graphics2D) g.create();
AffineTransform at = g2d.getTransform();
at.translate(originX, originY);
g2d.setTransform(at);
g2d.scale(scale, scale);
g2d.rotate(Math.toRadians(angle), 0, 0);
g2d.fillRect(xOffset, yOffset, rectWidth, rectHeight);
g2d.dispose();
Addendum: As #MadProgrammer observes, "If I remove the controls from the GUI, it appears … in the center." Indeed, the observed horizontal offset is precisely the preferred width of the slider in BorderLayout.WEST. I suspect that the origin is adjusted after a resize to meet the Component#paintAll() contract more easily: "The origin of the graphics context, its (0, 0) coordinate point, is the top-left corner of this component."

Change JButton gradient color, but only for one button, not all

I want to change JButton gradient color,
i found this, http://java2everyone.blogspot.com/2009/01/set-jbutton-gradient-color.html, but i want to change gradient for only one button, not all button
You can override the paintComponent method of the JButton instance and paint its Graphics object with one of the following classes that implement the Paint interface:
GradientPaint.
LinearGradientPaint
MultipleGradientPaint
RadialGradientPaint
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public final class JGradientButtonDemo {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
createAndShowGUI();
}
});
}
private static void createAndShowGUI() {
final JFrame frame = new JFrame("Gradient JButton Demo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(new FlowLayout());
frame.add(JGradientButton.newInstance());
frame.setSize(new Dimension(300, 150)); // used for demonstration
//frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private static class JGradientButton extends JButton {
private JGradientButton() {
super("Gradient Button");
setContentAreaFilled(false);
setFocusPainted(false); // used for demonstration
}
#Override
protected void paintComponent(Graphics g) {
final Graphics2D g2 = (Graphics2D) g.create();
g2.setPaint(new GradientPaint(
new Point(0, 0),
Color.WHITE,
new Point(0, getHeight()),
Color.PINK.darker()));
g2.fillRect(0, 0, getWidth(), getHeight());
g2.dispose();
super.paintComponent(g);
}
public static JGradientButton newInstance() {
return new JGradientButton();
}
}
}
A little improvement over mre answer:
private static final class JGradientButton extends JButton{
private JGradientButton(String text){
super(text);
setContentAreaFilled(false);
}
#Override
protected void paintComponent(Graphics g){
Graphics2D g2 = (Graphics2D)g.create();
g2.setPaint(new GradientPaint(
new Point(0, 0),
getBackground(),
new Point(0, getHeight()/3),
Color.WHITE));
g2.fillRect(0, 0, getWidth(), getHeight()/3);
g2.setPaint(new GradientPaint(
new Point(0, getHeight()/3),
Color.WHITE,
new Point(0, getHeight()),
getBackground()));
g2.fillRect(0, getHeight()/3, getWidth(), getHeight());
g2.dispose();
super.paintComponent(g);
}
}
TL;DR: it's not possible directly, but can be done with a workaround like in Luca's answer, however his/her answer uses the incorrect gradient steps. The correct ones are listed below.
The way it works
In the Metal LAF there is a hardcoded exception. If the background property is a subclass of UIResource, it's ignored* and the button is instead painted with the (also hardcoded) gradient from the UI property Button.gradient. Otherwise, if background is not a UIResource, that background is painted as-is.
*unless the button is disabled, in which case there is no gradient and the color inside the UIResource is used for the background.
The gradient
Following the logic of MetalButtonUI, I found out the used gradient it uses comes from the UI property Button.gradient, which contains the ArrayList:
0 = {Float} 0.3
1 = {Float} 0.0
2 = {ColorUIResource} "[221,232,243]"
3 = {ColorUIResource} "[255,255,255]"
4 = {ColorUIResource} "[184,207,229]"
Following the logic even further, I ended up in MetalUtils.GradientPainter.drawVerticalGradient(). This implementation interprets the above data as*:
Gradient from 0% to 30%: color1 to color2
Gradient from 30% to 60%: color2 to color1
Gradient from 60% to 100%: color1 to color3
*assuming the second float is 0.0, otherwise more gradients are drawn.
Since this is a multi-stage gradient, it can't be done with a simple GradientPaint but can be done with a LinearGradientPaint. However the background property only accepts Color. It cannot even be spoofed/hacked because the actual value is eventually given to Graphics.setColor() and not Graphics2D.setPaint() (even though Metal is Swing-based and not AWT) Dead End. The only solution seems to subclass JButton altogether.

Categories

Resources