diff --git a/src/edu/macalester/graphics/CanvasWindow.java b/src/edu/macalester/graphics/CanvasWindow.java index ed12fe1..ebbe0be 100644 --- a/src/edu/macalester/graphics/CanvasWindow.java +++ b/src/edu/macalester/graphics/CanvasWindow.java @@ -78,7 +78,7 @@ public class CanvasWindow { private final Canvas canvas; private final JFrame windowFrame; - private final GraphicsGroup content = new GraphicsGroup(); + private final GraphicsGroup content; private Set embeddedComponents = Set.of(); private final Rectangle background; @@ -101,7 +101,12 @@ public class CanvasWindow { * @param windowHeight The height of the window's content area */ public CanvasWindow(String title, int windowWidth, int windowHeight) { - content.setCanvas(this); // propagates to descendants + content = new GraphicsGroup() { + @Override + public CanvasWindow getCanvas() { + return CanvasWindow.this; + } + }; // We use a Rectangle for the background because canvas.setBackground() triggers spurious // repaints, whereas this approach puts background color changes into the same paint cycle diff --git a/src/edu/macalester/graphics/FrameRateReporter.java b/src/edu/macalester/graphics/FrameRateReporter.java index 68ba13b..14878d9 100644 --- a/src/edu/macalester/graphics/FrameRateReporter.java +++ b/src/edu/macalester/graphics/FrameRateReporter.java @@ -6,7 +6,7 @@ public class FrameRateReporter { private int framesSinceLastReport = 0; private long timeOfLastReport = System.currentTimeMillis(); - public void tick() { + public synchronized void tick() { if (!enabled) { return; } diff --git a/src/edu/macalester/graphics/GraphicsGroup.java b/src/edu/macalester/graphics/GraphicsGroup.java index c603136..1d8ff0f 100644 --- a/src/edu/macalester/graphics/GraphicsGroup.java +++ b/src/edu/macalester/graphics/GraphicsGroup.java @@ -13,7 +13,7 @@ * The group defines its own coordinate system, so the positions of objects added to it are relative * to the whole group's position. *

- * Calling {@link setPosition(Point)} on a GraphicsGroup sets where the group’s local (0,0) shows up + * Calling {@link #setPosition(Point)} on a GraphicsGroup sets where the group’s local (0,0) shows up * within its parent. This means that a group’s position is not necessarily the upper left, the * center, or any other fixed relationship with the shapes inside the group. Instead, you determine * how the group’s graphics relate to the whole group’s position when you set the position of each @@ -61,7 +61,7 @@ public GraphicsGroup() { public void add(GraphicsObject gObject) { gObject.addObserver(this); children.add(gObject); - gObject.setCanvas(getCanvas()); + gObject.setParent(this); changed(); } @@ -85,7 +85,7 @@ public void add(GraphicsObject gObject, double x, double y) { */ public void remove(GraphicsObject gObject) { gObject.removeObserver(this); - gObject.setCanvas(null); + gObject.setParent(null); if (!children.removeIf(child -> child == gObject)) { throw new NoSuchElementException("The object to remove is not part of this graphics group. Either it is already removed, or it was never originally added."); } @@ -100,7 +100,7 @@ public void removeAll() { while (it.hasNext()) { GraphicsObject obj = it.next(); obj.removeObserver(this); - obj.setCanvas(null); + obj.setParent(null); it.remove(); } changed(); @@ -190,14 +190,6 @@ void forEachDescendant(Point origin, BiConsumer callback) } } - @Override - void setCanvas(CanvasWindow canvas) { - super.setCanvas(canvas); - for (GraphicsObject child : children) { - child.setCanvas(canvas); - } - } - @Override protected void changed() { boundsNeedUpdate(); diff --git a/src/edu/macalester/graphics/GraphicsObject.java b/src/edu/macalester/graphics/GraphicsObject.java index f90c991..bfe7c57 100644 --- a/src/edu/macalester/graphics/GraphicsObject.java +++ b/src/edu/macalester/graphics/GraphicsObject.java @@ -20,7 +20,7 @@ */ public abstract class GraphicsObject { private final List observers = new ArrayList<>(); - private CanvasWindow canvas; + private GraphicsGroup parent; private Point position = Point.ORIGIN; private double rotation = 0; @@ -458,18 +458,27 @@ void forEachDescendant(Point origin, BiConsumer callback) callback.accept(this, origin.add(getPosition())); } + /** + * Returns the group that contains this graphics object, or null if it does not belong to a group. + */ + public GraphicsGroup getParent() { + return parent; + } + /** * Returns the window that this Object is inside, or null if it does not belong to a window. */ - public final CanvasWindow getCanvas() { - return canvas; + public CanvasWindow getCanvas() { + return (parent == null) ? null : parent.getCanvas(); } - void setCanvas(CanvasWindow canvas) { - if (canvas != this.canvas && canvas != null && this.canvas != null) { - throw new IllegalStateException("Trying to add graphics object to two different windows"); + void setParent(GraphicsGroup parent) { + if (parent != this.parent && parent != null && this.parent != null) { + throw new IllegalStateException( + "Cannot add " + this.getClass().getSimpleName() + " to group," + + " because it already belongs to a different group"); } - this.canvas = canvas; + this.parent = parent; } /** diff --git a/test/edu/macalester/graphics/CanvasWindowTest.java b/test/edu/macalester/graphics/CanvasWindowTest.java index c77a35b..a8e7729 100644 --- a/test/edu/macalester/graphics/CanvasWindowTest.java +++ b/test/edu/macalester/graphics/CanvasWindowTest.java @@ -79,6 +79,29 @@ void getElementAt() throws IOException { assertEquals(null, canvas.getElementAt(new Point(41, 51))); } + @Test + void getCanvas() { + GraphicsGroup g1 = new GraphicsGroup(); + GraphicsGroup g2 = new GraphicsGroup(); + GraphicsGroup g3 = new GraphicsGroup(); + canvas = new CanvasWindow("getCanvas", 10, 10); + + assertEquals(null, g1.getCanvas()); // Never added + + g1.add(g2); + canvas.add(g1); + assertEquals(canvas, g1.getCanvas()); // Added directly + assertEquals(canvas, g2.getCanvas()); // Added indirectly via existing relationship + + g2.add(g3); + assertEquals(canvas, g3.getCanvas()); // Added indirectly after parent already in canvas + + g1.remove(g2); + assertEquals(canvas, g1.getCanvas()); + assertEquals(null, g2.getCanvas()); + assertEquals(null, g3.getCanvas()); + } + @Test void embeddedComponentHandling() throws IOException { canvas = new CanvasWindow("embeddedComponentHandling", 320, 60); diff --git a/test/edu/macalester/graphics/GraphicsGroupTest.java b/test/edu/macalester/graphics/GraphicsGroupTest.java index 7313070..c5843d0 100644 --- a/test/edu/macalester/graphics/GraphicsGroupTest.java +++ b/test/edu/macalester/graphics/GraphicsGroupTest.java @@ -36,6 +36,26 @@ public GraphicsObject getGraphicsObject() { return group; } + @Test + void parentRelationships() { + group = new GraphicsGroup(); + assertEquals(null, group.getParent()); + assertEquals(null, circle.getParent()); + + group.add(circle); + assertEquals(group, circle.getParent()); + assertEquals(null, group.getParent()); + + GraphicsGroup group2 = new GraphicsGroup(); + assertThrows(IllegalStateException.class, () -> group2.add(circle)); + + group.remove(circle); + assertEquals(null, circle.getParent()); + + group2.add(circle); + assertEquals(group2, circle.getParent()); + } + @RenderingTest void simple() { group = new GraphicsGroup();