What the is the "Rect" class? - java

I'm trying to complete a project for school. It is partially completed when I get it. I cannot test anything I've written (except the layout, by commenting-out the error). I have an error for "Rect cannot be resolved to a type". Thinking that I did something wrong, I found this complete code posted online, and thought I'd see what differences there are. I get the same errors here...lots of them. What gives? Note* I'm not trying to pass this program in, just trying to see how it works, since I want mine to do the same thing.
/**
* DrawRects.java
*
* Allows the user to enter a number of rectangles using mouse input.
* Keeps previous rectangles around.
* Inspired by a C++ class demo by THC
*
* #author Scot Drysdale on 4/19/00. Modified to a JApplet 1/16/2012
* Modified to add a "clear" button and use an ArrayList on 1/18/2012
*/
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
public class DrawRects extends JApplet implements MouseListener,
MouseMotionListener {
private static final long serialVersionUID = 1L;
private Point pressedPoint = null; // place where mouse pressed down
private Rect currentRect = null; // rectangle being dragged.
private ArrayList<Rect> boxes = new ArrayList<Rect>(); // a list of rectangles
private static final Color[] colors = { Color.red, Color.cyan, Color.magenta,
Color.yellow };
private int colorIndex = 0; // index into colors of current color
private JButton clearButton; // Button to clear the screen
private static final int APPLET_WIDTH = 520; // Width of the applet
private static final int APPLET_HEIGHT = 550; // Height of the applet
private static final int CANVAS_WIDTH = 500; // Width of the canvas
private static final int CANVAS_HEIGHT = 500; // Height of the applet
/**
* Initializes the applet
*/
public void init() {
addMouseListener(this);
addMouseMotionListener(this);
setSize(APPLET_WIDTH, APPLET_HEIGHT);
Container cp = getContentPane(); // Content pane holds components
cp.setLayout(new FlowLayout()); // Fill left to right, top to bottom
Canvas canvas = new Canvas();
cp.add(canvas); // The canvas is the only component
// Make a button to clear the canvas, set the button's background
// to cyan, and add it to the content pane.
clearButton = new JButton("Clear");
clearButton.setBackground(Color.cyan);
clearButton.addActionListener(canvas);
cp.add(clearButton);
setVisible(true); // Makes the applet (and its components) visible
}
// Captures the position at which the mouse is initially pressed.
// It creates a new currentRect object, because the previous one
// will have been added to the ListOfRects.
public void mousePressed(MouseEvent event) {
pressedPoint = event.getPoint();
currentRect = new Rect(pressedPoint.x, pressedPoint.y, 0, 0, Color.black);
}
/**
* Gets the current position of the mouse as it is dragged and draws a
* rectangle with this point and pressedPoint as corners. This creates a
* rubberbanding rectangle effect.
*
* #param event the event that caused this callback
*/
public void mouseDragged(MouseEvent event) {
if (currentRect != null) { // make sure that currentRect exists
Point pt = event.getPoint();
currentRect.setX(Math.min(pt.x, pressedPoint.x));
currentRect.setY(Math.min(pt.y, pressedPoint.y));
currentRect.setWidth(Math.abs(pt.x - pressedPoint.x));
currentRect.setHeight(Math.abs(pt.y - pressedPoint.y));
repaint();
}
}
/**
* Done dragging mouse, so add current Rect to ListOfRects.
*
* #param event the event that caused this callback
*/
public void mouseReleased(MouseEvent event) {
if (currentRect != null) { // make sure that currentRect exists
currentRect.setColor(colors[colorIndex]);
colorIndex = (colorIndex + 1) % colors.length;
boxes.add(currentRect);
currentRect = null; // currentRect now in the list, so can't reuse it
}
repaint();
}
// Provide empty definitions for unused event methods.
public void mouseClicked(MouseEvent event) {}
public void mouseEntered(MouseEvent event) {}
public void mouseExited(MouseEvent event) {}
public void mouseMoved(MouseEvent event) {}
/**
* The canvas to draw upon
*/
private class Canvas extends JPanel implements ActionListener {
private static final long serialVersionUID = 1L;
/**
* Constructor to choose preferred size
*/
public Canvas() {
// Canvas is a subclass of JPanel. The way we set the size of
// a JPanel is by the setPreferredSize method. It takes a reference to
// a Dimension object, which just packages together a width and height.
setPreferredSize(new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT));
}
/**
* Draw the rectangles
*
* #param page the graphics object to draw on
*/
public void paintComponent(Graphics page) {
super.paintComponent(page);
page.setColor(Color.black);
page.drawRect(0, 0, CANVAS_WIDTH - 1, CANVAS_HEIGHT - 1); // Draw border
for (Rect rectangle : boxes) // Draw the saved rectangles
rectangle.fill(page);
if (currentRect != null) // Draw the rectangle being dragged out (if exists)
currentRect.draw(page);
}
/**
* Handle the button - provide an actionListener
* #param event the event that caused this callback
*/
public void actionPerformed(ActionEvent event) {
boxes.clear();
repaint();
}
}
}

There's nothing in the SDK named Rect.
There is, of course, java.awt.Rectangle, which may help you on your quest.
However, it looks like Rect was just some other class in the project you grabbed that you forgot to grab the source for (or the author didn't make it available).
It wouldn't require a special import or anything if it was in the same package as DrawRects (the default package, by the looks of it) or if it was, say, an inner class of DrawRects (which it's not).

Related

Superimposed JPanels not lining up

I am trying to make a paintable JPanel, with optional gridline display. To do this, I have a custom JPanel, which makes another JPanel that holds just the gridlines. That way, I can show / hide the grid lines without ever removing what is on the canvas.
All seems to be working, except for some weird alignment issues I can seem to pin down. When I start drawing some squares, they are only a few pixels above the gridlines. How can I fix this so that both panels are displaying EXACTLY on top of one another?
Here is an example of the issue:
package com.carvethsolutions.guilib.customcomp;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
/**
* A custom component for displaying a grid
*/
public class GridCanvas extends JPanel implements MouseListener {
/**
* Width and height of the canvas, in pixels
*/
private int width, height;
/**
* How many pixels represent one square on the grid
*/
private int gridScale;
/**
* The separate panel that holds the grid lines
*/
private JPanel gridPanel;
private boolean gridLinesVisible = true;
/**
* Holds color selections
*/
private Paintbrush paintbrush;
public GridCanvas() {
super();
width = 500;
height = 500;
setupComponent();
}
public GridCanvas(int width, int height) {
super();
this.width = width;
this.height = height;
setupComponent();
}
/**
* Private function to prepare the component.
*/
private void setupComponent() {
gridScale = 50;
this.setPreferredSize(new Dimension(width,height));
this.setBackground(Color.WHITE);
this.addMouseListener(this);
gridPanel = new JPanel() {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
// Vertical Lines
for (int x = gridScale; x <= width; x += gridScale) {
g.drawLine(x, 0, x, height);
}
for (int y = gridScale; y <= height; y += gridScale) {
g.drawLine(0, y, width, y);
}
}
};
gridPanel.setVisible(gridLinesVisible);
gridPanel.setPreferredSize(this.getPreferredSize());
this.add(gridPanel);
this.setSize(gridPanel.getSize());
paintbrush = new Paintbrush(Color.black);
}
/**
* Enable or disable grid lines from appearing on the Canvas
*/
public void toggleGridlines() {
gridLinesVisible = !gridLinesVisible;
gridPanel.setVisible(gridLinesVisible);
}
/**
* Invoked when the mouse button has been clicked (pressed
* and released) on a component.
*
* #param e
*/
#Override
public void mouseClicked(MouseEvent e) {
int x = e.getX() / gridScale;
int y = e.getY() / gridScale;
System.out.println("mouseClicked Event : (" + x + ", " + y + ")");
this.getGraphics().setColor(paintbrush.getColor());
this.getGraphics().fillRect(x * gridScale, y * gridScale, gridScale, gridScale);
}
/**
* Invoked when a mouse button has been pressed on a component.
*
* #param e
*/
#Override
public void mousePressed(MouseEvent e) {
}
/**
* Invoked when a mouse button has been released on a component.
*
* #param e
*/
#Override
public void mouseReleased(MouseEvent e) {
}
/**
* Invoked when the mouse enters a component.
*
* #param e
*/
#Override
public void mouseEntered(MouseEvent e) {
}
/**
* Invoked when the mouse exits a component.
*
* #param e
*/
#Override
public void mouseExited(MouseEvent e) {
}
}
To do this, I have a custom JPanel, which makes another JPanel that holds just the gridlines. That way, I can show / hide the grid lines without ever removing what is on the canvas.
Well, that is the wrong approach. Your panel should have properties:
paint the gridlines or not
a 2D array to control which squares should be filled.
Your paint logic is wrong:
this.getGraphics().setColor(paintbrush.getColor());
this.getGraphics().fillRect(x * gridScale, y * gridScale, gridScale, gridScale);
You should never use getGraphics(...) on a component to do custom painting. The first time Swing determines the components needs to be repainted you will lose all the painting. For example if you resize the frame.
Instead. custom painting must be done in the paintComponent() of the component. Then you:
first paint the gridlines if required
iterate through the 2D array and paint any squares as required.
Read the section from the Swing tutorial on Custom Painting for more information and working examples.
Or instead of the 2D Array, use an ArrayList to contain objects to be painted. This object could simply be a Point object to control the x/y location of a square to be painted, or is could be an object that contains complete information about the object to be painted such as location/color/shape. Check out Custom Painting Approaches for a working example of this approach.

JFrame with a Transparent Background Flickers when using ComponentResizer

Problem: An undecorated JFrame with a transparent background flickers when using a ComponentResizer to resize it. As seen in the below video and MCVE, the problem does not occur with an opaque background.
ComponentResizer (A MouseAdapter) works by calculating the drag distance and direction when the mouse is dragged and changes the size of its component accordingly.
The answer to What causes the Jframe to flicker while resizing? links to How to stop the auto-repaint() when I resize the Jframe, which says to turn of dynamic layout with Toolkit.getDefaultToolkit().setDynamicLayout(false), however, this does not solve the problem as it has no effect, possibly because macOS is not a platform that allows it to be disabled.
Question: How can I allow the user to resize an undecorated JFrame with a transparent background without it flickering? Is ComponentResizer the problem?
MCVE: (Length due to ComponentResizer class)
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JToggleButton;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.WindowConstants;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GraphicsEnvironment;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.HashMap;
import java.util.Map;
public class JFrameFlickerMCVE {
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
Toolkit.getDefaultToolkit().setDynamicLayout(false);
final JFrame frame = new JFrame();
frame.setUndecorated(true);
final JToggleButton backgroundButton = new JToggleButton("Break me!");
backgroundButton.setSelected(true);
backgroundButton.addActionListener(e -> {
if(!backgroundButton.isSelected()) {
frame.setBackground(new Color(0, 0, 0, 0));
backgroundButton.setText("Fix me!");
} else {
frame.setBackground(UIManager.getColor("control"));
backgroundButton.setText("Break me!");
}
});
final JLabel label = new JLabel("Resize Here");
label.setBorder(BorderFactory.createLineBorder(Color.RED));
frame.getContentPane().add(backgroundButton);
frame.getContentPane().add(label, BorderLayout.SOUTH);
new ComponentResizer(frame);
frame.pack();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
/**
* The ComponentResizer allows you to resize a component by dragging a border
* of the component.
*/
public static class ComponentResizer extends MouseAdapter
{
private final static Dimension MINIMUM_SIZE = new Dimension(10, 10);
private final static Dimension MAXIMUM_SIZE =
new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
private static Map<Integer, Integer> cursors = new HashMap<Integer, Integer>();
{
cursors.put(1, Cursor.N_RESIZE_CURSOR);
cursors.put(2, Cursor.W_RESIZE_CURSOR);
cursors.put(4, Cursor.S_RESIZE_CURSOR);
cursors.put(8, Cursor.E_RESIZE_CURSOR);
cursors.put(3, Cursor.NW_RESIZE_CURSOR);
cursors.put(9, Cursor.NE_RESIZE_CURSOR);
cursors.put(6, Cursor.SW_RESIZE_CURSOR);
cursors.put(12, Cursor.SE_RESIZE_CURSOR);
}
private Insets dragInsets;
private Dimension snapSize;
private int direction;
protected static final int NORTH = 1;
protected static final int WEST = 2;
protected static final int SOUTH = 4;
protected static final int EAST = 8;
private Cursor sourceCursor;
private boolean resizing;
private Rectangle bounds;
private Point pressed;
private boolean autoscrolls;
private Dimension minimumSize = MINIMUM_SIZE;
private Dimension maximumSize = MAXIMUM_SIZE;
/**
* Convenience contructor. All borders are resizable in increments of
* a single pixel. Components must be registered separately.
*/
public ComponentResizer()
{
this(new Insets(5, 5, 5, 5), new Dimension(1, 1));
}
/**
* Convenience contructor. All borders are resizable in increments of
* a single pixel. Components can be registered when the class is created
* or they can be registered separately afterwards.
*
* #param components components to be automatically registered
*/
public ComponentResizer(Component... components)
{
this(new Insets(5, 5, 5, 5), new Dimension(1, 1), components);
}
/**
* Convenience contructor. Eligible borders are resisable in increments of
* a single pixel. Components can be registered when the class is created
* or they can be registered separately afterwards.
*
* #param dragInsets Insets specifying which borders are eligible to be
* resized.
* #param components components to be automatically registered
*/
public ComponentResizer(Insets dragInsets, Component... components)
{
this(dragInsets, new Dimension(1, 1), components);
}
/**
* Create a ComponentResizer.
*
* #param dragInsets Insets specifying which borders are eligible to be
* resized.
* #param snapSize Specify the dimension to which the border will snap to
* when being dragged. Snapping occurs at the halfway mark.
* #param components components to be automatically registered
*/
public ComponentResizer(Insets dragInsets, Dimension snapSize, Component... components)
{
setDragInsets( dragInsets );
setSnapSize( snapSize );
registerComponent( components );
}
/**
* Get the drag insets
*
* #return the drag insets
*/
public Insets getDragInsets()
{
return dragInsets;
}
/**
* Set the drag dragInsets. The insets specify an area where mouseDragged
* events are recognized from the edge of the border inwards. A value of
* 0 for any size will imply that the border is not resizable. Otherwise
* the appropriate drag cursor will appear when the mouse is inside the
* resizable border area.
*
* #param dragInsets Insets to control which borders are resizeable.
*/
public void setDragInsets(Insets dragInsets)
{
validateMinimumAndInsets(minimumSize, dragInsets);
this.dragInsets = dragInsets;
}
/**
* Get the components maximum size.
*
* #return the maximum size
*/
public Dimension getMaximumSize()
{
return maximumSize;
}
/**
* Specify the maximum size for the component. The component will still
* be constrained by the size of its parent.
*
* #param maximumSize the maximum size for a component.
*/
public void setMaximumSize(Dimension maximumSize)
{
this.maximumSize = maximumSize;
}
/**
* Get the components minimum size.
*
* #return the minimum size
*/
public Dimension getMinimumSize()
{
return minimumSize;
}
/**
* Specify the minimum size for the component. The minimum size is
* constrained by the drag insets.
*
* #param minimumSize the minimum size for a component.
*/
public void setMinimumSize(Dimension minimumSize)
{
validateMinimumAndInsets(minimumSize, dragInsets);
this.minimumSize = minimumSize;
}
/**
* Remove listeners from the specified component
*
* #param component the component the listeners are removed from
*/
public void deregisterComponent(Component... components)
{
for (Component component : components)
{
component.removeMouseListener( this );
component.removeMouseMotionListener( this );
}
}
/**
* Add the required listeners to the specified component
*
* #param component the component the listeners are added to
*/
public void registerComponent(Component... components)
{
for (Component component : components)
{
component.addMouseListener( this );
component.addMouseMotionListener( this );
}
}
/**
* Get the snap size.
*
* #return the snap size.
*/
public Dimension getSnapSize()
{
return snapSize;
}
/**
* Control how many pixels a border must be dragged before the size of
* the component is changed. The border will snap to the size once
* dragging has passed the halfway mark.
*
* #param snapSize Dimension object allows you to separately spcify a
* horizontal and vertical snap size.
*/
public void setSnapSize(Dimension snapSize)
{
this.snapSize = snapSize;
}
/**
* When the components minimum size is less than the drag insets then
* we can't determine which border should be resized so we need to
* prevent this from happening.
*/
private void validateMinimumAndInsets(Dimension minimum, Insets drag)
{
int minimumWidth = drag.left + drag.right;
int minimumHeight = drag.top + drag.bottom;
if (minimum.width < minimumWidth
|| minimum.height < minimumHeight)
{
String message = "Minimum size cannot be less than drag insets";
throw new IllegalArgumentException( message );
}
}
/**
*/
#Override
public void mouseMoved(MouseEvent e)
{
Component source = e.getComponent();
Point location = e.getPoint();
direction = 0;
if (location.x < dragInsets.left)
direction += WEST;
if (location.x > source.getWidth() - dragInsets.right - 1)
direction += EAST;
if (location.y < dragInsets.top)
direction += NORTH;
if (location.y > source.getHeight() - dragInsets.bottom - 1)
direction += SOUTH;
// Mouse is no longer over a resizable border
if (direction == 0)
{
source.setCursor( sourceCursor );
}
else // use the appropriate resizable cursor
{
int cursorType = cursors.get( direction );
Cursor cursor = Cursor.getPredefinedCursor( cursorType );
source.setCursor( cursor );
}
}
#Override
public void mouseEntered(MouseEvent e)
{
if (! resizing)
{
Component source = e.getComponent();
sourceCursor = source.getCursor();
}
}
#Override
public void mouseExited(MouseEvent e)
{
if (! resizing)
{
Component source = e.getComponent();
source.setCursor( sourceCursor );
}
}
#Override
public void mousePressed(MouseEvent e)
{
// The mouseMoved event continually updates this variable
if (direction == 0) return;
// Setup for resizing. All future dragging calculations are done based
// on the original bounds of the component and mouse pressed location.
resizing = true;
Component source = e.getComponent();
pressed = e.getPoint();
SwingUtilities.convertPointToScreen(pressed, source);
bounds = source.getBounds();
// Making sure autoscrolls is false will allow for smoother resizing
// of components
if (source instanceof JComponent)
{
JComponent jc = (JComponent)source;
autoscrolls = jc.getAutoscrolls();
jc.setAutoscrolls( false );
}
}
/**
* Restore the original state of the Component
*/
#Override
public void mouseReleased(MouseEvent e)
{
resizing = false;
Component source = e.getComponent();
source.setCursor( sourceCursor );
if (source instanceof JComponent)
{
((JComponent)source).setAutoscrolls( autoscrolls );
}
}
/**
* Resize the component ensuring location and size is within the bounds
* of the parent container and that the size is within the minimum and
* maximum constraints.
*
* All calculations are done using the bounds of the component when the
* resizing started.
*/
#Override
public void mouseDragged(MouseEvent e)
{
if (resizing == false) return;
Component source = e.getComponent();
Point dragged = e.getPoint();
SwingUtilities.convertPointToScreen(dragged, source);
changeBounds(source, direction, bounds, pressed, dragged);
}
protected void changeBounds(Component source, int direction, Rectangle bounds, Point pressed, Point current)
{
// Start with original locaton and size
int x = bounds.x;
int y = bounds.y;
int width = bounds.width;
int height = bounds.height;
// Resizing the West or North border affects the size and location
if (WEST == (direction & WEST))
{
int drag = getDragDistance(pressed.x, current.x, snapSize.width);
int maximum = Math.min(width + x, maximumSize.width);
drag = getDragBounded(drag, snapSize.width, width, minimumSize.width, maximum);
x -= drag;
width += drag;
}
if (NORTH == (direction & NORTH))
{
int drag = getDragDistance(pressed.y, current.y, snapSize.height);
int maximum = Math.min(height + y, maximumSize.height);
drag = getDragBounded(drag, snapSize.height, height, minimumSize.height, maximum);
y -= drag;
height += drag;
}
// Resizing the East or South border only affects the size
if (EAST == (direction & EAST))
{
int drag = getDragDistance(current.x, pressed.x, snapSize.width);
Dimension boundingSize = getBoundingSize( source );
int maximum = Math.min(boundingSize.width - x, maximumSize.width);
drag = getDragBounded(drag, snapSize.width, width, minimumSize.width, maximum);
width += drag;
}
if (SOUTH == (direction & SOUTH))
{
int drag = getDragDistance(current.y, pressed.y, snapSize.height);
Dimension boundingSize = getBoundingSize( source );
int maximum = Math.min(boundingSize.height - y, maximumSize.height);
drag = getDragBounded(drag, snapSize.height, height, minimumSize.height, maximum);
height += drag;
}
source.setBounds(x, y, width, height);
source.validate();
}
/*
* Determine how far the mouse has moved from where dragging started
*/
private int getDragDistance(int larger, int smaller, int snapSize)
{
int halfway = snapSize / 2;
int drag = larger - smaller;
drag += (drag < 0) ? -halfway : halfway;
drag = (drag / snapSize) * snapSize;
return drag;
}
/*
* Adjust the drag value to be within the minimum and maximum range.
*/
private int getDragBounded(int drag, int snapSize, int dimension, int minimum, int maximum)
{
while (dimension + drag < minimum)
drag += snapSize;
while (dimension + drag > maximum)
drag -= snapSize;
return drag;
}
/*
* Keep the size of the component within the bounds of its parent.
*/
private Dimension getBoundingSize(Component source)
{
if (source instanceof Window)
{
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
Rectangle bounds = env.getMaximumWindowBounds();
return new Dimension(bounds.width, bounds.height);
}
else
{
return source.getParent().getSize();
}
}
}
}
I searched for a solution but no one was working...
The reason seems to be the redrawing of all component tree during the resize user action.
As the redrawing of the resized component and of its children components is done not only after mouse released (end of the resize) but during the resize too (during mouse dragging) I tried to comment the
source.validate();
line into
changeBounds method (it is continuously called during dragging).
I put the line at the beginning of mouseReleased method into a if block:
if(resizing == true) {
e.getComponent().validate();
}
It works (the flickering is almost absent).
Let me know if this solution works for you too...

PopUp Class Seems To Speed Up Animations

I have a class called PopUp, which is a JPanel that, when activated, expands to a given size and location based on the parameters given from the center and contracts the same way when clicked.
For some reason, when the PopUp is expanding and contracting, the animations sharing the same JPanel speed up. I've witnessed this on the two programs I've used my PopUp class on.
Here is what I believe the relevant code is:
/**
* The {#code PopUp} class is a JPanel that expands to the rectangle
* created from the given x, y, width and height that expands from
* the center of the rectangle.
* <p>
* Here is an example of how the {#code PopUp} object can be initialized:
* <blockquote><pre>
* PopUp pu = new PopUp(25, 25, 575, 575, 25, Color.GRAY);
* </pre></blockquote>
* <p>
* The class {#code PopUp} includes methods for drawing the pop-up;
* choosing whether the pop-up is expanding or not; getting the
* percentage that the pop-up is expanded; and getting the maximum x, y,
* width, and height
*
* #author Gigi Bayte 2
*/
public class PopUp extends JPanel implements MouseListener {
private static final long serialVersionUID = 1L;
/**
* Expanded x coordinate
*/
private double x;
/**
* Expanded y coordinate
*/
private double y;
/**
* Expanded width value
*/
private double width;
/**
* Expanded height value
*/
private double height;
/**
* Number of steps until fully expanded
*/
private int steps;
/**
* This divided by steps is the percentage the pop-up is expanded
*/
private int expansionStage = 0;
/**
* Whether or not the pop-up is expansing
*/
private boolean isExpanding = false;
/**
* Color of the pop-up
*/
private Color color;
/**
* The rectangle that represents the bounds of the pop-up
*/
private Rectangle2D popUp;
/**
* Initializes a newly created {#code PopUp} with a uniform color
* #param x The x coordinate of the expanded pop-up
* #param y The y coordinate of the expanded pop-up
* #param w The width of the expanded pop-up
* #param h The height of the expanded pop-up
* #param expansionSteps The number of steps until fully expanded
* #param popUpColor The color of the pop-up
*/
public PopUp(double x, double y, double w, double h, int expansionSteps, Color popUpColor) {
this.x = x;
this.y = y;
width = w;
height = h;
color = popUpColor;
steps = expansionSteps;
popUp = new Rectangle2D.Double(0, 0, width, height);
addMouseListener(this);
}
/**
* Draws the pop-up
* #param g Graphics object from paintComponent
*/
public final void draw(Graphics g) {
if(isExpanding)
expansionStage = Math.min(expansionStage + 1, steps);
else
expansionStage = Math.max(expansionStage - 1, 0);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
AffineTransform trans = new AffineTransform();
trans.translate(x + width/2 * (1 - (double) expansionStage/steps), y + height/2 * (1 - (double) expansionStage/steps));
trans.scale((double) expansionStage/steps, (double) expansionStage/steps);
setBounds((int) trans.getTranslateX(), (int) trans.getTranslateY(), (int) (width * expansionStage/steps), (int) (height * expansionStage/steps));
g2d.setColor(color);
Shape transformed = trans.createTransformedShape(popUp);
g2d.fill(transformed);
}
/**
* Sets whether the pop-up is expanding or not
* #param expanding Whether or not the pop-up should expand
*/
public final void setExpanding(boolean expanding) {
isExpanding = expanding;
}
#Override
public final void mouseClicked(MouseEvent e) {
isExpanding = false;
}
}
Here is a test class to run:
public class Test extends JPanel implements ActionListener, MouseListener {
private static final long serialVersionUID = 1L;
private static PopUp popUp;
private int stringX = 610;
private int stringCounter = 0;
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setSize(600, 600);
Test t = new Test();
t.setBounds(0, 0, 600, 600);
frame.add(t);
t.setVisible(true);
Timer timer = new Timer(5, t);
popUp = new PopUp(100, 100, 400, 400, 100, Color.WHITE);
frame.add(popUp);
popUp.setVisible(true);
timer.start();
frame.addMouseListener(t);
frame.setLayout(null);
frame.setUndecorated(true);
frame.setVisible(true);
}
#Override
public void actionPerformed(ActionEvent e) {
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLACK);
g.fillRect(0, 0, 600, 600);
popUp.draw(g);
g.setColor(Color.WHITE);
g.drawString("This is a test", stringX, 580);
if(++stringCounter % 3 == 0) {
--stringX;
stringCounter = 0;
}
if(stringX == -10 - g.getFontMetrics().stringWidth("This is a test"))
stringX = 610;
}
#Override
public void mouseClicked(MouseEvent e) {
popUp.setExpanding(!popUp.getExpanding());
}
#Override
public void mousePressed(MouseEvent e) {}
#Override
public void mouseReleased(MouseEvent e) {}
#Override
public void mouseEntered(MouseEvent e) {}
#Override
public void mouseExited(MouseEvent e) {}
}
As can be seen in the above example, the text scrolling from right to left speeds up every time the pop-up is expanding or contracting.
That is the expected behavior when receiving repeated update events that invoke paintComponent(); resize this AnimationTest to reproduce the effect.
Why exactly do the repeated update events cause this? What's the logic behind it?
Each call to setBounds() in your draw() method "invalidates the component hierarchy." The Component API ensures that
When the hierarchy gets invalidated, like after changing the bounds of components, or adding/removing components to/from containers, the whole hierarchy must be validated afterwards by means of the Container.validate() method invoked on the top-most invalid container of the hierarchy.
Because the validate() method "may be a quite time-consuming operation," you can "postpone the validation of the hierarchy till a set of layout-related operations completes," as you show here; or you can pace the animation with a javax.swing.Timer, illustrated here.
Well, I found the problem. The culprit was the line here:
setBounds((int) trans.getTranslateX(), (int) trans.getTranslateY(), (int) (width * expansionStage/steps), (int) (height * expansionStage/steps));
Apparently scaling a JPanel to different sizes in rapid succession causes some speed warping for reasons I do not know. I'd appreciate an edit to this answer with a better explanation for this phenomenon.
I just set a static size for the JPanel and had the graphics do the rest of the work.

MouseEvent getX and getY offset from actual coordinate

I'm coding a basic paint program and I have been having trouble with Rectangle and Ellipse tools. If you click and drag you should be able to draw the shape with the dimensions based on the startpoint and endpoint (both use getX() and getY()), the problem being that these two shapes get the startpoint right but the endpoint is offset in both the x and y coordinates.
This code below is pretty much the same as the code that I used in my line tool (which works properly) except swapping Line2D with Rectangle2D and Ellipse2D respectively.
package tools;
import gui.DrawingPanel;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Rectangle2D;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ImageIcon;
/**
* Creates the Rectangle Action.
*#version 1
*/
public class RectangleAction extends AbstractAction {
private final DrawingPanel myPanel;
private Rectangle2D.Double myRectangle;
private double Start_X;
private double Start_Y;
/**
* Constructor for Rectangle Action.
*/
public RectangleAction(final DrawingPanel thePanel) {
super("Rectangle", getImageIcon());
myPanel = thePanel;
putValue(Action.MNEMONIC_KEY, KeyEvent.VK_R);
putValue(Action.SELECTED_KEY, true);
}
#Override
public void actionPerformed(final ActionEvent theEvent) {
myPanel.addMouseListener(new MyMouseListener());
myPanel.addMouseMotionListener(new MyMouseListener());
}
/**
* gets the image icon of the action.
* #return the image icon.
*/
public static ImageIcon getImageIcon() {
return new ImageIcon("./images/rectangle.gif");
}
/**
* Listens for mouse clicks, to draw on our panel.
*/
private class MyMouseListener extends MouseAdapter {
private double myX2;
private double myY2;
/**
* Handles a click event.
*
* #param theEvent The event.
*/
#Override
public void mousePressed(final MouseEvent theEvent) {
Start_X = (double) theEvent.getX();
Start_Y = (double) theEvent.getY();
}
/**
* Handles the release event.
*
* #param theEvent The event.
*/
#Override
public void mouseReleased(final MouseEvent theEvent) {
myX2 = (double) theEvent.getX();
myY2 = (double) theEvent.getY();
myRectangle = new Rectangle2D.Double(Start_X, Start_Y, myX2, myY2);
myPanel.setShape(myRectangle);
myPanel.repaint();
}
/**
* Handles a click event.
*
* #param theEvent The event.
*/
#Override
public void mouseDragged(final MouseEvent theEvent) {
myX2 = (double) theEvent.getX();
myY2 = (double) theEvent.getY();
myRectangle = new Rectangle2D.Double(Start_X, Start_Y, myX2, myY2);
myPanel.setShape(myRectangle);
myPanel.repaint();
}
}
}
I should note that I did look at this similar question but it didn't give me the answer I was looking for; also the DrawingPanel is just a JPanel with a Paint Component to draw the shape and nothing else.
myRectangle = new Rectangle2D.Double(Start_X, Start_Y, myX2, myY2);
The parameters are (x, y, width, height) you are trying to specify two points.
Your painting logic assumes you always drag the mouse from top/left to bottom/right. It is always possible the mouse could be dragged up and left which would result in negative values when you calculate the width/height based on the two points.
This is code I have used to calculate the Rectangle bounds correctly:
int x = Math.min(startPoint.x, e.getX());
int y = Math.min(startPoint.y, e.getY());
int width = Math.abs(startPoint.x - e.getX());
int height = Math.abs(startPoint.y - e.getY());
You don't need to create two listeners, you can just share the same listener:
//myPanel.addMouseListener(new MyMouseListener());
//myPanel.addMouseMotionListener(new MyMouseListener());
MouseAdapter myMouseAdapter = new MyMouseListener();
myPanel.addMouseListener( myMouseAdapter );
myPanel.addMouseMotionListener( myMouseAdapter);
Also, you keep adding the adapter to the panel every time you click on the button. So if you click on you line tool, then the ellispse tool and then the rectangle tool you will have 3 listener added to the panel. I would suggest you should remove all listeners from the panel before adding your listener for the current tool.
You're initializing the rectangle with the x and y of release instead of width and height.
Replace
myRectangle = new Rectangle2D.Double(Start_X, Start_Y, myX2, myY2);
with
int x;
int y;
if (Start_X > myX2) {
x = myX2;
} else {
x = Start_X;
}
if (Start_Y > myY2) {
y = myY2;
} else {
y = Start_Y;
}
myRectangle = new Rectangle2D.Double(x, y, Math.abs(myX2 - Start_X), Math.abs(myY2 - Start_Y));

Overriding JButton paintComponent with transparency not showing back panel color

Here is what I am trying to do. I have extended JButton and overwritten the paintComponent method creating my desired effect of a rounded edge button, and a color fading effect when the button is rolled over by a mouse. All that works great. My problem is that the JButton is still painting a white rectangle area as the images show.
I would like 1) the white corners to be gone and 2) the cetner of the button to show the panel behind it. Here is what I have tried:
1- when painting the button use getParent().getBackground() and paint the button first. This works great for opaque panels. However I would love this button to work on a partially or fully transparent panel. With transparent panels it paints the color, but on the white background hiding anything behind the panel (like an image).
2- I have tried many combinations of setOpaque(false) or setContentAreaFilled(false). I have tried this while calling super.paintComponent(g) and not calling it. None of those seem to work.
3- The button looks right when I don't use the method g2.clearRect(0,0,width,height) (clearing the graphics area before painting), but since the graphics object is never covered up the fade effect stops working after one rollover of the button.
4- I use a JLabel for the text and have tried setting it opaque or just not using it and the issue still remains. so I don't think that is the issue.
Since I only want an affect for the JButton and not other swing components I'm really hoping to avoid making my own ButtonUI.
Thanks and I hope this makes sense. Below is the code for my button.
import javax.swing.*;
import javax.swing.Timer;
import java.awt.*;
import java.awt.event.*;
/**
* WButton.java
*
* An extension of JButton but with custom graphics
*
*/
public class WButton extends JButton{
private Timer timer;
private float[] background = {.3f,.6f,.8f,0f};
private boolean fadeUp = true;
private boolean fadeDown = false;
private JLabel label;
/**
* Default Constructor
*/
public WButton(){
super();
label = new JLabel();
setupButton();
}
/**
* Text constructor
*/
public WButton(String text){
super(text);
label = new JLabel(text);
setupButton();
}
/**
* common setup functions
*/
private void setupButton(){
timer = new Timer(24,new TimerAction(this));
label.setLabelFor(this);
add(label);
}
/**
* Set the background color
*/
#Override
public void setBackground(Color bg){
background = bg.getRGBComponents(background);
background[3] = 0f;
super.setBackground(new Color(background[0],background[1],
background[2],background[3]));
repaint();
}
/**
* get background
*/
#Override
public Color getBackground(){
if(background!=null)
return new Color(background[0],background[1],background[2],background[3]);
return new Color(.5f,.5f,.5f);
}
/**
* Set the font of the button
*/
#Override
public void setFont(Font font){
super.setFont(font);
if(label!=null)
label.setFont(font);
}
/**
* Override the set text method
*/
#Override
public void setText(String t){
super.setText(t);
if(label!=null)
label.setText(t);
}
/**
* Paint the button
*/
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
int width = getWidth();
int height = getHeight();
Graphics2D g2 = (Graphics2D)g;
g2.clearRect(0,0,width,height);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
//Check Button Model state
if(model.isPressed())
paintPressedButton(g2,width,height);
else{
if(model.isRollover()){
if(fadeUp){
fadeUp = false;
timer.start();
}
}
else{
if(fadeDown){
fadeDown = false;
timer.start();
}
}
g2.setPaint(new Color(background[0],background[1],background[2],background[3]));
g2.fillRoundRect(0,0,width-1,height-1,height,height);
}
}
/**
* Draw a pressed button
*/
private void paintPressedButton(Graphics2D g2,int width,int height){
float[] temp = new float[4];
for(int i=0;i<background.length;i++)
temp[i] = background[i]-.4f < 0f ? 0f : background[i]-.4f;
g2.setPaint(new Color(temp[0],temp[1],temp[2],temp[3]));
g2.fillRoundRect(0,0,width-1,height-1,height,height);
}
/**
* paint the border
*/
public void paintBorder(Graphics g){
int width = getWidth();
int height = getHeight();
g.setColor(Color.BLACK);
g.drawRoundRect(0,0,width-1,height-1,height,height);
}
/**
* Inner action listener class
*/
private class TimerAction implements ActionListener{
private float alphaInc = .2f;
WButton button;
public TimerAction(WButton b){
button = b;
}
public void actionPerformed(ActionEvent e){
if(model.isRollover()){
background[3] += alphaInc;
if(background[3] > 1.0f){
timer.stop();
background[3] = 1.0f;
fadeDown = true;
}
}
else{
background[3] -= alphaInc;
if(background[3] < 0f){
timer.stop();
background[3] = 0f;
fadeUp = true;
}
}
button.repaint();
}
}
}
EDIT 1
What aly suggested got me closer, but not quite there. Instead of g2.clearRect() I painted the object with a transparent color as suggested. The white box is gone, but a different color is there. Upon investigation is is the color of the parent panel but with no transparency. Here are pictures for an example (the panel has 70% transparency). The first pictures is when the program starts. The second picture is after 1 mouse rollover.
What you could do is instead of using clearRect(), clear the background with a completely transparent color.
g2.setColor(new Color(0,0,0,0));
g2.drawRect(0,0,width,height);
You still need to setOpaque(false) on the JButton so that it doesn't use the blue rollover color as the background once you hover over it once.
Edit: After seeing what you just posted, I think the problem is that the main frame isn't repainted.
Try:
SwingUtilities.getWindowAncestor(this).repaint();
in the paint method to repaint the frame, that might fix the problem.
The problem here is, swing uses paintImmediately(x, y, width, height) on JButton to repaint the changed area. If you look inside that method, it iterates through the parent hierarchy (if parent is not opaque) until it finds an opaque component. Then call repaint on it. As you may notice, this approach don't take the alpha component in background color into account because those components are opaque (isOpaque = true).
To address this issue, you can override the paintImmediately() method in JButton as follows:
#Override
public void paintImmediately(int x, int y, int w, int h) {
super.paintImmediately(x, y, w, h);
Component component = this;
boolean repaint = false;
while (!component.isOpaque() || (component.getBackground() != null && component.getBackground().getAlpha() < 255)) {
if (component.getBackground() != null && component.getBackground().getAlpha() < 255) {
repaint = true;
}
if (component.getParent() == null) {
break;
}
component = component.getParent();
if (!(component instanceof JComponent)) {
break;
}
}
// There is no need to repaint if no component with an alpha component in
// background is found and no parent component is found (implied by component != this)
// since super.paintImmediately() should have handled the general case.
if (repaint && component != this) {
component.repaint();
}
}
This method will check for the parent of the topmost component which is either not opaque or has a transparent background and repaints it. Performance point of view, this is much better than the currently accepted answer (which is redrawing the entire window, everytime when a button is hovered/pressed).

Categories

Resources