I have two ArrayList instances that contain some data and I want that data to be displayed as boxes or their data by using Swing.
The end goal is to display both array lists as one matrix of junctions and roads.
I have an object that contains both:
Map map = new Map(10);
System.out.println(map.calcShortestPath(map.getJunctions().get(4), map.getJunctions().get(0)));
System.out.println("\n Map #2");
ArrayList<Junction> junctions = new ArrayList<Junction>();
junctions.add(new Junction(0, 0));
junctions.add(new Junction(0, 3));
junctions.add(new Junction(4, 3));
junctions.add(new Junction(4, 0));
ArrayList<Road> roads = new ArrayList<Road>();
roads.add(new Road(junctions.get(0), junctions.get(1)));
roads.add(new Road(junctions.get(1), junctions.get(2)));
roads.add(new Road(junctions.get(2), junctions.get(3)));
roads.add(new Road(junctions.get(3), junctions.get(0)));
roads.add(new Road(junctions.get(0), junctions.get(2)));
map = new Map(junctions, roads);
The things I have tried so far:
Using JTable - didn't seem to be the correct choice for this.
Using JList - didn't seem to work as I tried to see one of the lists with this
code:
JList<Juncion> displayList = new JList<>(junctions.toArray(new String[0]));
JScrollPane scrollPane = new JScrollPane(displayList);
getContentPane().add(scrollPane);
setDefaultCloseOperation(EXIT_ON_CLOSE);
pack();
setVisible(true);
I think I'm getting closer to this by using the JList but I think I'm not doing this the correct way.
I thought it might be interesting to whip up an example of a graph display.
Here's the GUI I created.
The junctions are represented by squares, and the roads are represented by lines.
The first thing I did was create a Graph class to hold a List of junctions and a List of roads. I had to guess what the numbers of the Junction class represented. I assumed they were X and Y coordinates.
Once I created the Graph class (model class), writing the drawing panel and the paintComponent method was straightforward.
Here's the code. You would need to modify it to display more than one graph.
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class GraphDisplay implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new GraphDisplay());
}
private DrawingPanel drawingPanel;
private Graph graph;
private JFrame frame;
public GraphDisplay() {
this.graph = new Graph();
}
#Override
public void run() {
frame = new JFrame("Graph Display");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
drawingPanel = new DrawingPanel(graph);
frame.add(drawingPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = 1L;
private Graph graph;
private Tuple xTuple;
private Tuple yTuple;
public DrawingPanel(Graph graph) {
this.graph = graph;
this.xTuple = graph.getXRange();
this.yTuple = graph.getYRange();
this.setBackground(Color.WHITE);
this.setPreferredSize(new Dimension(400, 400));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int xSpacing = getWidth() / (xTuple.getMaximum() -
xTuple.getMinimum() + 2);
int ySpacing = getHeight() / (yTuple.getMaximum() -
yTuple.getMinimum() + 2);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.BLACK);
g2d.setStroke(new BasicStroke(5f));
Font font = getFont().deriveFont(16f);
g2d.setFont(font);
List<Junction> junctions = graph.getJunctions();
for (int i = 0; i < junctions.size(); i++) {
Junction junction = junctions.get(i);
int x = (junction.getX() + 1) * xSpacing;
int y = (junction.getY() + 1) * ySpacing;
g.drawRect(x - 16, y - 16, 32, 32);
}
List<Road> roads = graph.getRoads();
for (int i = 0; i < roads.size(); i++) {
Road road = roads.get(i);
Junction origin = road.getOrigin();
Junction destination = road.getDestination();
int x1 = (origin.getX() + 1) * xSpacing;
int y1 = (origin.getY() + 1) * ySpacing;
int x2 = (destination.getX() + 1) * xSpacing;
int y2 = (destination.getY() + 1) * ySpacing;
g2d.drawLine(x1, y1, x2, y2);
}
}
}
public class Graph {
private final List<Junction> junctions;
private final List<Road> roads;
public Graph() {
junctions = new ArrayList<Junction>();
junctions.add(new Junction(0, 0));
junctions.add(new Junction(0, 3));
junctions.add(new Junction(4, 3));
junctions.add(new Junction(4, 0));
roads = new ArrayList<Road>();
roads.add(new Road(junctions.get(0), junctions.get(1)));
roads.add(new Road(junctions.get(1), junctions.get(2)));
roads.add(new Road(junctions.get(2), junctions.get(3)));
roads.add(new Road(junctions.get(3), junctions.get(0)));
roads.add(new Road(junctions.get(0), junctions.get(2)));
}
public List<Junction> getJunctions() {
return junctions;
}
public List<Road> getRoads() {
return roads;
}
public Tuple getXRange() {
int minimum = junctions.get(0).getX();
int maximum = minimum;
for (int i = 1; i < junctions.size(); i++) {
int x = junctions.get(i).getX();
minimum = Math.min(minimum, x);
maximum = Math.max(maximum, x);
}
return new Tuple(minimum, maximum);
}
public Tuple getYRange() {
int minimum = junctions.get(0).getY();
int maximum = minimum;
for (int i = 1; i < junctions.size(); i++) {
int y = junctions.get(i).getY();
minimum = Math.min(minimum, y);
maximum = Math.max(maximum, y);
}
return new Tuple(minimum, maximum);
}
}
public class Road {
private final Junction origin;
private final Junction destination;
public Road(Junction origin, Junction destination) {
this.origin = origin;
this.destination = destination;
}
public Junction getOrigin() {
return origin;
}
public Junction getDestination() {
return destination;
}
}
public class Junction {
private final int x;
private final int y;
public Junction(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}
public class Tuple {
private final int minimum;
private final int maximum;
public Tuple(int minimum, int maximum) {
this.minimum = minimum;
this.maximum = maximum;
}
public int getMinimum() {
return minimum;
}
public int getMaximum() {
return maximum;
}
}
}
I'm working on a Venn Diagram GUI application in Java which requires me to create elements (JLabels) that can be dragged and then dropped in the circular Venn Diagram sections. Presently I have two (custom made) circular J Panels that are overlapping.
Two Questions Arise: 1) How can I create J Labels which can move around the screen and ultimately snap into certain bounded spots of the Label and fit within bounds. 2) How am going to create a layout on the J Panels themselves that will only allow the text elements to fit in the certain spots and not overlap.
Here's what my GUI looks like so far. I've marked it up to show you a general idea of the interface and how I would like the functionality to work
Here you go... About 463 lines of code to get you started...
public class Final {
public static enum MouseEventFlag {
KEY_ALT, KEY_ALTGRAPH, KEY_CTRL, KEY_META, KEY_SHIFT, BUTTON_LEFT, BUTTON_MIDDLE, BUTTON_RIGHT;
public static EnumSet<MouseEventFlag> buttonSetOf(final MouseEvent mevt) {
final EnumSet<MouseEventFlag> set = EnumSet.noneOf(MouseEventFlag.class);
if (SwingUtilities.isLeftMouseButton(mevt)) set.add(BUTTON_LEFT);
if (SwingUtilities.isMiddleMouseButton(mevt)) set.add(BUTTON_MIDDLE);
if (SwingUtilities.isRightMouseButton(mevt)) set.add(BUTTON_RIGHT);
return set;
}
}
//Converts EnumSet to mask:
public static long pack(final EnumSet<?> set) { //Supports Enums with up to 64 values.
//return set.stream().mapToLong(e -> (1L << e.ordinal())).reduce(0L, (L1, L2) -> L1 | L2);
long L = 0;
for (final Enum e: set)
L = L | (1L << e.ordinal());
return L;
}
//Converts Enums to mask:
public static <E extends Enum<E>> long pack(final E... ez) { //Supports Enums with up to 64 values.
long L = 0;
for (final Enum e: ez)
L = L | (1L << e.ordinal());
return L;
}
public static Color transparent(final Color c, final int alpha) {
return new Color(c.getRed(), c.getGreen(), c.getBlue(), alpha);
}
public static void setOperatedSize(final Dimension input, final BiFunction<Integer, Integer, Integer> operator, final Dimension inputOutput) {
inputOutput.setSize(operator.apply(input.width, inputOutput.width), operator.apply(input.height, inputOutput.height));
}
//Prompts user to input some text.
public static void inputTitle(final Component parent, final String titleID, final Consumer<String> consumer) {
final String userIn = JOptionPane.showInputDialog(parent, "Enter " + titleID.toLowerCase() + "'s title:", "Enter title", JOptionPane.QUESTION_MESSAGE);
if (userIn != null) {
if (userIn.isEmpty())
JOptionPane.showMessageDialog(parent, titleID + "'s name cannot be empty...", "Oups!", JOptionPane.INFORMATION_MESSAGE);
else
consumer.accept(userIn);
}
}
//Applies an action to every child component of the container recursively as well as to the given Container.
public static void consumeComponentsRecursively(final Container container, final Consumer<Component> consumer) {
for (final Component child: container.getComponents())
if (child instanceof Container)
consumeComponentsRecursively((Container) child, consumer);
else
consumer.accept(child);
consumer.accept(container);
}
public static Dimension getGoodEnoughSize(final Component comp, final Dimension defaultSize) {
final Dimension dim = new Dimension(defaultSize);
if (comp != null) { // && comp.isVisible()) {
/*Start with default size, and then listen to max and min
(if both max and min are set, we prefer the min one):*/
if (comp.isMaximumSizeSet())
setOperatedSize(comp.getMaximumSize(), Math::min, dim);
if (comp.isMinimumSizeSet())
setOperatedSize(comp.getMinimumSize(), Math::max, dim);
}
return dim;
}
public static class ManualLayout implements LayoutManager, Serializable {
public Dimension getLayoutComponentSize(final Component comp) {
return getGoodEnoughSize(comp, (comp.getWidth() <= 0 && comp.getHeight() <= 0)? comp.getPreferredSize(): comp.getSize());
}
#Override public void addLayoutComponent(final String name, final Component comp) { }
#Override public void removeLayoutComponent(final Component comp) { }
#Override public Dimension preferredLayoutSize(final Container parent) { return minimumLayoutSize(parent); } //Preferred and minimum coincide for simplicity.
#Override
public Dimension minimumLayoutSize(final Container parent) {
final Component[] comps = parent.getComponents();
if (comps == null || comps.length <= 0)
return new Dimension();
final Rectangle totalBounds = new Rectangle(comps[0].getLocation(), getLayoutComponentSize(comps[0]));
for (int i = 1; i < comps.length; ++i)
totalBounds.add(new Rectangle(comps[i].getLocation(), getLayoutComponentSize(comps[i])));
return new Dimension(totalBounds.x + totalBounds.width, totalBounds.y + totalBounds.height);
}
#Override
public void layoutContainer(final Container parent) {
for (final Component comp: parent.getComponents())
comp.setSize(getLayoutComponentSize(comp)); //Just set the size. The locations are taken care by the class's client supposedly.
}
}
public static abstract class RectangularPanel<R extends RectangularShape> extends JPanel {
private R shape;
private Area cache; /*Use a cache so as not to have to create new Areas every time we need
to intersect the clip (see 'createClip' method). Also, the client can modify the current
shape but not the cache upon which the shape and painting of the panel depend.*/
public RectangularPanel(final double width, final double height) {
super();
super.setOpaque(false);
cache = new Area(shape = createShape(0, 0, width, height));
super.addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(final ComponentEvent cevt) {
cache = new Area(shape = createShape(0, 0, getWidth(), getHeight()));
revalidate();
repaint();
}
});
}
protected abstract R createShape(final double x, final double y, final double width, final double height);
protected abstract double getArcWidth();
protected abstract double getArcHeight();
protected final R getShape() { return shape; }
#Override public boolean contains(final int x, final int y) { return cache.contains(x, y); }
protected Shape createClip(final Shape originalClip) {
if (originalClip == null)
return cache;
final Area clip = new Area(originalClip);
clip.intersect(cache);
return clip;
}
#Override
public void paint(final Graphics g) { //print() and update() rely on paint(), so we only need to override this one...
g.setClip(createClip(g.getClip()));
super.paint(g);
}
}
public static class VennTool implements Runnable {
protected static final Object LAYER_USER_CONTROLS = JLayeredPane.DEFAULT_LAYER, LAYER_VENN_SET = JLayeredPane.PALETTE_LAYER, LAYER_VENN_LABEL = JLayeredPane.MODAL_LAYER;
public class VennDrawPanel extends JPanel {
private final JLayeredPane pane;
private final int drawingOffsetY, drawingOffsetX;
private final JCheckBox attachMode, collisionMode;
private final JPanel ctrl;
public VennDrawPanel(final GraphicsConfiguration gconf) {
super(new BorderLayout());
pane = new JLayeredPane();
super.add(pane, BorderLayout.CENTER);
final ManualLayout layout = new ManualLayout();
pane.setLayout(layout);
final Dimension prefsz = new Dimension(gconf.getBounds().getSize());
prefsz.width = (2 * prefsz.width) / 3;
prefsz.height = (2 * prefsz.height) / 3;
final JButton createLabel = new JButton("Create label");
final JButton createSet = new JButton("Create set");
attachMode = new JCheckBox("Attach mode", true);
collisionMode = new JCheckBox("Collision mode", true);
ctrl = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 5));
ctrl.add(createLabel);
ctrl.add(createSet);
ctrl.add(attachMode);
ctrl.add(collisionMode);
drawingOffsetX = layout.getLayoutComponentSize(ctrl).width;
prefsz.width = Math.max(prefsz.width, drawingOffsetX);
drawingOffsetY = prefsz.height / 8;
pane.setPreferredSize(prefsz);
pane.add(ctrl, LAYER_USER_CONTROLS);
createLabel.addActionListener(e -> inputTitle(this, "Label", VennTool.this::createLabel));
createSet.addActionListener(e -> inputTitle(this, "Set", VennTool.this::createSet));
}
protected void setControlsEnabled(final boolean enable) { consumeComponentsRecursively(ctrl, c -> c.setEnabled(enable)); }
public boolean isAttachModeSelected() { return attachMode.isSelected(); }
public boolean isCollisionModeSelected() { return collisionMode.isSelected(); }
protected Point getCreationLocation() { return new Point(drawingOffsetX + 50, drawingOffsetY / 2); }
public void addSet(final VennSet set) {
set.setLocation(getCreationLocation());
pane.add(set, LAYER_VENN_SET, 0);
pane.revalidate();
pane.repaint();
}
public void addLabel(final VennLabel label) {
label.setLocation(getCreationLocation());
pane.add(label, LAYER_VENN_LABEL, 0);
pane.revalidate();
pane.repaint();
}
}
protected static class VennBorder extends LineBorder {
public VennBorder(final int thickness) { super(Color.BLACK, thickness, true); }
#Override
public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int width, final int height) {
if (c instanceof VennControl) {
final VennControl ctrl = (VennControl) c;
Graphics2D g2d = (Graphics2D) g.create();
try {
g2d.setColor(ctrl.getBorderColor());
final int t2 = thickness + thickness;
final int aw = (int) Math.round(Math.min(width, ctrl.getArcWidth())), ah = (int) Math.round(Math.min(height, ctrl.getArcHeight()));
final Path2D path = new Path2D.Float(Path2D.WIND_EVEN_ODD);
path.append(new RoundRectangle2D.Float(x, y, width, height, aw, ah), false);
path.append(new RoundRectangle2D.Double(x + thickness, y + thickness, width - t2, height - t2, aw, ah), false);
g2d.fill(path);
}
finally {
g2d.dispose();
}
}
else
super.paintBorder(c, g, x, y, width, height);
}
}
public static <C1 extends VennControl<C1, C2, ?, ?>, C2 extends VennControl<C2, C1, ?, ?>> void attach(final C1 c1, final C2 c2) { //Utility method.
c1.getAttachments().add(c2);
c2.getAttachments().add(c1);
}
public static <C1 extends VennControl<C1, C2, ?, ?>, C2 extends VennControl<C2, C1, ?, ?>> void detach(final C1 c1, final C2 c2) { //Utility method.
c1.getAttachments().remove(c2);
c2.getAttachments().remove(c1);
}
protected abstract class VennControl<C1 extends VennControl<C1, C2, R1, R2>, C2 extends VennControl<C2, C1, R2, R1>, R1 extends RectangularShape, R2 extends RectangularShape> extends RectangularPanel<R1> {
private Color bg;
private boolean highlighted, selected;
private final LinkedHashSet<C2> attachments;
public VennControl(final String title, final double width, final double height) {
super(width, height);
super.setLayout(new GridBagLayout());
super.add(new JLabel(Objects.toString(title), JLabel.CENTER));
super.setBorder(new VennBorder(2));
super.setSize((int) Math.ceil(width), (int) Math.ceil(height));
attachments = new LinkedHashSet<>();
bg = transparent(Color.LIGHT_GRAY, 127);
highlighted = selected = false;
final MouseAdapter relocationMA = new MouseAdapter() {
private Point moveAnchor;
private LinkedHashSet<C2> intersections;
#Override
public void mousePressed(final MouseEvent mevt) {
if (pack(MouseEventFlag.buttonSetOf(mevt)) == pack(MouseEventFlag.BUTTON_LEFT) && moveAnchor == null && intersections == null) {
VennTool.this.drawPanel.setControlsEnabled(false);
moveAnchor = mevt.getPoint();
setSelected(true);
intersections = findIntersections(0, 0);
intersections.forEach(c2 -> c2.setHighlighted(true));
}
}
#Override
public void mouseDragged(final MouseEvent mevt) {
final int dx = mevt.getX() - moveAnchor.x, dy = mevt.getY() - moveAnchor.y;
final boolean attach = VennTool.this.drawPanel.isAttachModeSelected(), collisions = VennTool.this.drawPanel.isCollisionModeSelected();
final LinkedHashSet<C2> newIntersections = findIntersections(dx, dy);
if (MouseEventFlag.buttonSetOf(mevt).contains(MouseEventFlag.BUTTON_LEFT) && moveAnchor != null && intersections != null && (!attach || newIntersections.containsAll(getAttachments())) && (!collisions || !collides(dx, dy))) {
setLocation(getX() + dx, getY() + dy);
LinkedHashSet<C2> setHighlight = (LinkedHashSet<C2>) intersections.clone();
setHighlight.removeAll(newIntersections);
if (!attach)
setHighlight.forEach(c2 -> detach(c2, (C1) VennControl.this));
setHighlight.forEach(c2 -> c2.setHighlighted(false));
setHighlight = (LinkedHashSet<C2>) newIntersections.clone();
setHighlight.removeAll(intersections);
setHighlight.forEach(c2 -> c2.setHighlighted(true));
intersections = newIntersections;
}
}
#Override
public void mouseReleased(final MouseEvent mevt) {
if (pack(MouseEventFlag.buttonSetOf(mevt)) == pack(MouseEventFlag.BUTTON_LEFT) && moveAnchor != null && intersections != null) {
intersections.forEach(c2 -> c2.setHighlighted(false));
final VennDrawPanel vdp = VennTool.this.drawPanel;
if (vdp.isAttachModeSelected())
intersections.forEach(c2 -> attach(c2, (C1) VennControl.this));
moveAnchor = null;
intersections = null;
setSelected(false);
vdp.setControlsEnabled(true);
}
}
};
super.addMouseListener(relocationMA);
super.addMouseMotionListener(relocationMA);
}
protected LinkedHashSet<C2> findIntersections(final double dx, final double dy) {
final R1 r1tmp = getShape();
final R1 r1 = createShape(getX() + dx + r1tmp.getX(), getY() + dy + r1tmp.getY(), r1tmp.getWidth(), r1tmp.getHeight());
final LinkedHashSet<C2> intersections = new LinkedHashSet<>(), possibilities = getPossibleIntersections();
possibilities.forEach(c2 -> {
final R2 r2tmp = c2.getShape();
final R2 r2 = c2.createShape(c2.getX() + r2tmp.getX(), c2.getY() + r2tmp.getY(), r2tmp.getWidth(), r2tmp.getHeight());
if (intersect(r1, r2))
intersections.add(c2);
});
return intersections;
}
public LinkedHashSet<C2> getAttachments() { return attachments; }
protected abstract boolean intersect(final R1 r1, final R2 r2);
protected abstract LinkedHashSet<C2> getPossibleIntersections();
protected abstract boolean collides(final double dx, final double dy);
public void setHighlighted(final boolean highlighted) {
if (highlighted != this.highlighted) {
this.highlighted = highlighted;
repaint();
}
}
public void setSelected(final boolean selected) {
if (selected != this.selected) {
this.selected = selected;
repaint();
}
}
public void setColor(final Color c) {
if (!bg.equals(c)) {
bg = Objects.requireNonNull(c);
repaint();
}
}
public Color getBorderColor() {
return selected? Color.GREEN: (highlighted? Color.CYAN: Color.BLACK);
}
#Override
protected void paintComponent(final Graphics g) {
super.paintComponent(g);
g.setColor(bg);
g.fillRect(0, 0, getWidth(), getHeight());
}
}
protected class VennLabel extends VennControl<VennLabel, VennSet, Rectangle2D, Ellipse2D> {
public VennLabel(final String title) { super(title, 0, 0); }
#Override protected Rectangle2D createShape(double x, double y, double width, double height) { return new Rectangle2D.Double(x, y, width, height); }
#Override protected double getArcWidth() { return 0; }
#Override protected double getArcHeight() { return 0; }
#Override protected boolean intersect(final Rectangle2D r1, final Ellipse2D r2) { return r2.intersects(r1); }
#Override protected LinkedHashSet<VennSet> getPossibleIntersections() { return VennTool.this.sets; }
#Override
protected boolean collides(final double dx, final double dy) {
Rectangle2D tmp = getShape();
final Rectangle2D thisShape = createShape(getX() + dx + tmp.getX(), getY() + dy + tmp.getY(), tmp.getWidth(), tmp.getHeight());
for (final VennLabel label: VennTool.this.labels)
if (label != this) {
tmp = label.getShape();
tmp = label.createShape(label.getX() + tmp.getX(), label.getY() + tmp.getY(), tmp.getWidth(), tmp.getHeight());
if (tmp.intersects(thisShape))
return true;
}
return false;
}
}
protected class VennSet extends VennControl<VennSet, VennLabel, Ellipse2D, Rectangle2D> {
public VennSet(final String title, final double radius) {
super(title, radius + radius, radius + radius);
final Dimension sz = super.getPreferredSize();
sz.width = sz.height = Math.max(Math.max(sz.width, super.getWidth()), Math.max(sz.height, super.getHeight()));
super.setSize(sz);
}
#Override protected Ellipse2D createShape(double x, double y, double width, double height) { return new Ellipse2D.Double(x, y, width, height); }
#Override protected double getArcWidth() { return getWidth(); }
#Override protected double getArcHeight() { return getHeight(); }
#Override protected boolean intersect(final Ellipse2D r1, final Rectangle2D r2) { return r1.intersects(r2); }
#Override protected LinkedHashSet<VennLabel> getPossibleIntersections() { return VennTool.this.labels; }
#Override protected boolean collides(final double dx, final double dy) { return false; } //Never collides with anything.
}
private final JFrame frame;
private final VennDrawPanel drawPanel;
private final LinkedHashSet<VennSet> sets;
private final LinkedHashSet<VennLabel> labels;
public VennTool(final GraphicsConfiguration gconf) {
drawPanel = new VennDrawPanel(gconf);
frame = new JFrame("Collisionless Venn Tool", gconf);
sets = new LinkedHashSet<>();
labels = new LinkedHashSet<>();
}
public void createSet(final String title) {
final VennSet set = new VennSet(title, 100);
sets.add(set);
drawPanel.addSet(set);
drawPanel.revalidate();
drawPanel.repaint();
}
public void createLabel(final String title) {
final VennLabel label = new VennLabel(title);
labels.add(label);
drawPanel.addLabel(label);
drawPanel.revalidate();
drawPanel.repaint();
}
#Override
public void run() {
if (SwingUtilities.isEventDispatchThread()) {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(drawPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
else
SwingUtilities.invokeLater(this);
}
}
public static void main(final String[] args) {
final VennTool tool = new VennTool(GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration());
SwingUtilities.invokeLater(() -> {
tool.createSet("Drag this set!");
tool.createLabel("Drag this label!");
});
tool.run();
}
}
Why so many lines?
Mainly because you said what you have implemented but did not provide any code. So I had to design everything from scratch.
From a custom layout manager, to custom shaped panels and borders, to the actual VennTool, VennDrawPanel, VennSet and VennLabel representations.
So what does it include?
Here is something like a class diagram which is my best effort so far for your requirements:
Boxes represent classes, pointers represent inheritance and nested boxes represent nested classes. Black text represents class names and cyan text represents comments/details.
The code starts with seemingly unrelated classes, but they act something like a framework to work with in the VennTool.
To start, MouseEventFlag enum is just there to convert MouseEvents to enum constants so as to test which button/key is pressed for each mouse event.
Then there are some static methods that do general things like packing EnumSets to long masks, converting colors to transparent, obtaining and operating on Component sizes etc...
Then there is the custom LayoutManager class, named ManualLayout. As the name suggests, you have to put the Components of a Container with a ManualLayout to where you want them to be with (for example) setBounds or setLocation methods. Then the layout calculates a preferred size for each Component in the Container in order to return minimum and preferred layout sizes. The layoutContainer method simply sets the sizes of the included Components and does not move their location. The ManualLayout takes into account all sizes of each Component (ie current size, preferred, minimum and maximum size). Instance of such a layout is used to lay out the Components in the drawing panel (will talk about that in this post).
Then there is the RectangularPanel class which is a custom shaped JPanel which supports any java.awt.geom.RectangularShape shape to shape out the panel itself and paint it. This is a superclass of the components which compose a Venn set (such as the Venn set itself and the labels added to the drawing panel).
Then there starts the VennTool class. This class implements Runnable to run the simulation. It encloses all the classes representing Venn components/elements as well as the drawing panel. Client classes are only supposed to see the VennTool class (which provides methods for creating Venn sets and labels) and nothing else (except if they are subclasses of it). Handling and modifying the Venn sets is up to the user. You can of course modify the class to return all the Venn components already in the simulation at any time you need to save them as a project for example later, but such a functionality is not implemented here. Just add some getters and file I/O to do this yourself.
Then, inside the VennTool now, is the VennDrawPanel which is the drawing panel which I was talking about all above, which is responsible for the layout of the whole simulation. Here we add the Venn sets, labels and user controls (such as buttons to let the users define their actions). The VennDrawPanel has a JLayeredPane which uses a ManualLayout, achieving different layers and locations for each component. In particular, the first layer is composed by the buttons. On top of that layer are the Venn sets and finally on top of that are the Venn labels. As a future idea, you can implement a new layer on which the dragged component will go upon dragging (for example you may want a Venn set which the user is currently dragging to be at the layer which is above all others).
Then, there is the custom Border class, named (you guessed it) VennBorder. It is enclosed in VennTool only to show the scope of it and to allow it interact with each Venn component. It's purely inpired by and also extending javax.swing.border.LineBorder class.
Then, there is the VennControl class which represents each Venn component (ie VennSets and VennLabels). It is responsible to consume mouse guestures (such as dragging a Venn set around), for collision detection and resolution of labels and also responsible for attaching/detaching sets to labels and labels to sets.
The custom VennBorder and VennControl classes are defined by a shape of type java.awt.geom.RectangularShape. This allows that each subclass can be for example of Ellipse2D shape (as in the case of VennSets which are circular) or a Rectangle2D (as in the case of VennLabels). It also defines an arc width and height to support RoundRectangle2D but that just came along the road to support both Ellipse2D and Rectangle2D in a single panel/border.
Finally, there is the VennSet and VennLabel classes which represent a Venn set and a label to be added to a Venn set correspondingly.
Attach/Detach mode:
While enabled, each mouse release will attach the label you hold with all overlapping sets (or the set you hold with all overlapping labels). It will also not let labels/sets go non-overlapping with their attached sets/labels. Disable this mode in order to move any VennControl freely around detaching it on mouse release.
Collision detection mode:
As the name suggests, when enabled, labels will collide and not overlap with each other. While when disabled, they will not.
I'd like to take Pane object from controller class to another class called Wykres in my case, then add to it children for example Circle's objects, but when I try to generate circles by clicking button there is no circles, can someone help me? Thanks.
public class Controller {
#FXML
public Label label_x_waga;
#FXML
Label label_y_waga;
#FXML
Pane root_pane;
#FXML
Button button_add;
private static Pane panee;
private SiecNeuronowa siecNeuronowa;
public void initialize(){
siecNeuronowa = new SiecNeuronowa();
panee=root_pane;
}
public static Pane getPane(){
return panee;
}
#FXML
public void button_add_action(){
root_pane = Wykres.getPane_root();
for(int i=0;i<300;i++)
siecNeuronowa.dodajPunkt();
}
}
And the Wykres class
public class Wykres {
private int xWartosc, yWartosc;
private String kolor;
private static Pane pane_root;
public void rysujPunkt(Pane root, int x, int y, String color){
x+=180;
y+=180;
xWartosc = x;
yWartosc = y;
this.kolor = color;
if(color.equals("NIEBIESKI")){
root.getChildren().add(new Circle(x, y, 5, Color.BLUE));
}
else if(color.equals("CZERWONY")){
root.getChildren().add(new Circle(x, y, 5, Color.RED));
}
pane_root = root;
}
public static Pane getPane_root(){return pane_root;}
}
rysujPunkt() method is invoked here:
public class SiecNeuronowa {
private Perceptron perceptron1;
private Wykres wykres1;
public SiecNeuronowa(){
wykres1 = new Wykres();
perceptron1 = new Perceptron(){
#Override
public double finalizuj_dane(double potencjalMembranowy){
if(potencjalMembranowy>0)
return 1;
else
if(potencjalMembranowy<0)
return -1;
else
return 0;
}
};
perceptron1.dodajWejscia(2);
Random random = new Random();
double rand_x = random.nextDouble();
double rand_y = random.nextDouble();
perceptron1.setWagaWejsciowa(0, rand_x);
perceptron1.setWagaWejsciowa(1, rand_y);
}
public void dodajPunkt(){
Random random = new Random();
int x = random.nextInt()*150;
int y = random.nextInt()*150;
perceptron1.setDaneWejsciowe(0, x);
perceptron1.setDaneWejsciowe(1, y);
int wynik = (int)perceptron1.getWyjscie();
if(wynik == 1)
wykres1.rysujPunkt(Controller.getPane(), x, y, "Niebieski");
else
if(wynik == -1)
wykres1.rysujPunkt(Controller.getPane(), x, y, "Czerwony");
}
}
I'm trying to implement undo/redo in JavaFX - I draw all my shapes using graphicsContext(). I have looked around and found that there's a save method on Graphics Context but it just saves attributes and not the actual shape/state of the canvas. What would be the best way of going about this?
This is one of my code snippets when I create a circle, for instance:
public CircleDraw(Canvas canvas, Scene scene, BorderPane borderPane) {
this.borderPane = borderPane;
this.scene = scene;
this.graphicsContext = canvas.getGraphicsContext2D();
ellipse = new Ellipse();
ellipse.setStrokeWidth(1.0);
ellipse.setFill(Color.TRANSPARENT);
ellipse.setStroke(Color.BLACK);
pressedDownMouse = event -> {
startingPosX = event.getX();
startingPosY = event.getY();
ellipse.setCenterX(startingPosX);
ellipse.setCenterY(startingPosY);
ellipse.setRadiusX(0);
ellipse.setRadiusY(0);
borderPane.getChildren().add(ellipse);
};
releasedMouse = event -> {
borderPane.getChildren().remove(ellipse);
double width = Math.abs(event.getX() - startingPosX);
double height = Math.abs(event.getY() - startingPosY);
graphicsContext.setStroke(Color.BLACK);
graphicsContext.strokeOval(Math.min(startingPosX, event.getX()), Math.min(startingPosY, event.getY()), width, height);
removeListeners();
};
draggedMouse = event -> {
ellipse.setCenterX((event.getX() + startingPosX) / 2);
ellipse.setCenterY((event.getY() + startingPosY) / 2);
ellipse.setRadiusX(Math.abs((event.getX() - startingPosX) / 2));
ellipse.setRadiusY(Math.abs((event.getY() - startingPosY) / 2));
};
}
The problem here is that there is that information like this is not saved in a Canvas. Furthermore there is no inverse operation that allows you to get back to the previous state for every draw information. Surely you could stroke the same oval, but with backgrund color, however the information from previous drawing information could have been overwritten, e.g. if you're drawing multiple intersecting ovals.
You could store the drawing operations using the command pattern however. This allows you to redraw everything.
public interface DrawOperation {
void draw(GraphicsContext gc);
}
public class DrawBoard {
private final List<DrawOperation> operations = new ArrayList<>();
private final GraphicsContext gc;
private int historyIndex = -1;
public DrawBoard(GraphicsContext gc) {
this.gc = gc;
}
public void redraw() {
Canvas c = gc.getCanvas();
gc.clearRect(0, 0, c.getWidth(), c.getHeight());
for (int i = 0; i <= historyIndex; i++) {
operations.get(i).draw(gc);
}
}
public void addDrawOperation(DrawOperation op) {
// clear history after current postion
operations.subList(historyIndex+1, operations.size()).clear();
// add new operation
operations.add(op);
historyIndex++;
op.draw(gc);
}
public void undo() {
if (historyIndex >= 0) {
historyIndex--;
redraw();
}
}
public void redo() {
if (historyIndex < operations.size()-1) {
historyIndex++;
operations.get(historyIndex).draw(gc);
}
}
}
class EllipseDrawOperation implements DrawOperation {
private final double minX;
private final double minY;
private final double width;
private final double height;
private final Paint stroke;
public EllipseDrawOperation(double minX, double minY, double width, double height, Paint stroke) {
this.minX = minX;
this.minY = minY;
this.width = width;
this.height = height;
this.stroke = stroke;
}
#Override
public void draw(GraphicsContext gc) {
gc.setStroke(stroke);
gc.strokeOval(minX, minY, width, height);
}
}
Pass a DrawBoard instance to your class instead of the Canvas and replace
graphicsContext.setStroke(Color.BLACK);
graphicsContext.strokeOval(Math.min(startingPosX, event.getX()), Math.min(startingPosY, event.getY()), width, height);
with
drawBoard.addDrawOperation(new EllipseDrawOperation(
Math.min(startingPosX, event.getX()),
Math.min(startingPosY, event.getY()),
width,
height,
Color.BLACK));
The undo and redo to move through the history.
I am new to graphics in java and I am currently working on a game. Essentially, there are rising bubbles, and the user has to pop them by moving the mouse over them.
I have already made the bubbles but now for the collision detection, I need to be able to find the x and coordinates as well as the diameter of the circle. How can I return the instance variables for each bubble (from the Bubbles object array). You can see my code below. If you find coding errors, please let me know.
Game Class:
public class Game extends JPanel{
public static final int WINDOW_WIDTH = 600;
public static final int WINDOW_HEIGHT = 400;
private boolean insideCircle = false;
private static boolean ifPaused = false;
private static JPanel mainPanel;
private static JLabel statusbar;
private static int clicks = 0;
//Creates a Bubbles object Array
Bubbles[] BubblesArray = new Bubbles[5];
public Game() {
//initializes bubble objects
for (int i = 0; i < BubblesArray.length; i++)
BubblesArray[i] = new Bubbles();
}
public void paint(Graphics graphics) {
//makes background white
graphics.setColor(Color.WHITE);
graphics.fillRect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
//paints square objects to the screen
for (Bubbles aBubblesArray : BubblesArray) {
aBubblesArray.paint(graphics);
}
}
public void update() {
//calls the Square class update method on the square objects
for (Bubbles aBubblesArray : BubblesArray) aBubblesArray.update();
}
public static void main(String[] args) throws InterruptedException {
statusbar = new JLabel("Default");
Game game = new Game();
game.addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
statusbar.setText(String.format("Your mouse is at %d, %d", e.getX(), e.getY()));
}
public void mouseExited(MouseEvent e){
ifPaused = true;
}
public void mouseEntered(MouseEvent e){
ifPaused = false;
}
});
JFrame frame = new JFrame();
frame.add(game);
frame.add(statusbar, BorderLayout.SOUTH);
frame.setVisible(true);
frame.setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setTitle("Bubble Burst");
frame.setResizable(false);
frame.setLocationRelativeTo(null);
while (true) {
if (!ifPaused){
game.update();
game.repaint();
Thread.sleep(5);
}
}
}
}
Bubbles Class:
public class Bubbles{
private int circleXLocation;
private int circleSize;
private int circleYLocation = Game.WINDOW_WIDTH + circleSize;
private int fallSpeed = 1;
private int color = (int) (4 * Math.random() + 1);
Random rand = new Random();
private int whereMouseX;
private int whereMouseY;
public int generateRandomXLocation(){
return circleXLocation = (int) (Game.WINDOW_WIDTH * Math.random() - circleSize);
}
public int generateRandomCircleSize(){
return circleSize = (int) (50 * Math.random() + 30);
}
public int generateRandomFallSpeed(){
return fallSpeed = (int) (5 * Math.random() + 1);
}
public void paint(Graphics g){
if (color == 1) g.setColor(new Color(0x87CEEB));
if (color == 2) g.setColor(new Color(0x87CEFF));
if (color == 3) g.setColor(new Color(0x7EC0EE));
if (color == 4) g.setColor(new Color(0x6CA6CD));
g.fillOval (circleXLocation, circleYLocation, circleSize, circleSize);
}
public Bubbles(){
generateRandomXLocation();
generateRandomCircleSize();
generateRandomFallSpeed();
}
public void update(){
if (circleYLocation <= - circleSize){
generateRandomXLocation();
generateRandomFallSpeed();
generateRandomCircleSize();
circleYLocation = Game.WINDOW_HEIGHT + circleSize;
}
if (circleYLocation > - circleSize){
circleYLocation -= fallSpeed;
}
}
public int getX(){
return circleXLocation;
}
public int getY(){
return circleYLocation;
}
public int getCircleSize(){
return circleSize;
}
}
Set your bubbles to include x and y values in it's constructor.
Public Bubble(float x, float y, int circleSize){
// initialize variables here
}
Then check to see if they are colliding with your current mouse location, something like...
if(e.getX() == this.getX() && e.getY() == this.getY()){
// do something
}
Loop through each Bubble in the array and access the public get methods you have created in the Bubble class:
Example:
for (Bubble b : BubblesArray)
{
int x = b.getX();
int y = b.getY();
}