diff --git a/hw4/src/CarShape.java b/hw4/src/CarShape.java new file mode 100644 index 0000000..2090f81 --- /dev/null +++ b/hw4/src/CarShape.java @@ -0,0 +1,66 @@ +import java.awt.*; +import java.awt.geom.*; + +/** + * A car shape composed of primitive shapes. + * NOTE: This code structure is based on typical textbook examples for drawing composite shapes. + * Specific implementation details might vary based on the exact textbook source. + */ +public class CarShape implements CompositeShape { + private final int x; // Base x-coordinate (relative to drawing origin) + private final int y; // Base y-coordinate (relative to drawing origin) + private final int width; + + // Default constructor positions at (0,0) with default width + public CarShape() { + this(0, 0, 60); // Default position and width + } + + // Constructor for position and width (not strictly needed if always drawn at origin for icon/panel) + public CarShape(int x, int y, int width) { + this.x = x; + this.y = y; + this.width = width; + } + + @Override + public int getWidth() { + return width; // Width of the car body + } + + @Override + public int getHeight() { + // Approximate height: body height + wheel radius + return width / 2 + width / 6; + } + + @Override + public void draw(Graphics2D g2) { + // All drawing is relative to (0,0) because translation happens externally + + // Car body + Rectangle2D.Double body = new Rectangle2D.Double(x, y + width / 6.0, width - 1, width / 6.0); + + // Roof + Point2D.Double r1 = new Point2D.Double(x + width / 6.0, y + width / 6.0); + Point2D.Double r2 = new Point2D.Double(x + width / 3.0, y); + Point2D.Double r3 = new Point2D.Double(x + width * 2.0 / 3.0, y); + Point2D.Double r4 = new Point2D.Double(x + width * 5.0 / 6.0, y + width / 6.0); + Line2D.Double L1 = new Line2D.Double(r1,r2); + Line2D.Double L2 = new Line2D.Double(r2,r3); + Line2D.Double L3 = new Line2D.Double(r3,r4); + + + // Wheels + Ellipse2D.Double frontTire = new Ellipse2D.Double(x + width / 6.0, y + width / 3.0, width / 6.0, width / 6.0); + Ellipse2D.Double rearTire = new Ellipse2D.Double(x + width * 2.0 / 3.0, y + width / 3.0, width / 6.0, width / 6.0); + + // Draw the parts + g2.draw(body); + g2.draw(L1); + g2.draw(L2); + g2.draw(L3); + g2.draw(frontTire); + g2.draw(rearTire); + } +} diff --git a/hw4/src/CompositeShape.java b/hw4/src/CompositeShape.java new file mode 100644 index 0000000..163ab9e --- /dev/null +++ b/hw4/src/CompositeShape.java @@ -0,0 +1,27 @@ +import java.awt.Graphics2D; + +/** + * An interface for shapes that can be composed of multiple primitive shapes + * and can be drawn. + */ +public interface CompositeShape { + /** + * Draws the shape. + * @param g2 the graphics context + */ + void draw(Graphics2D g2); + + /** + * Gets the width of the shape's bounding box. + * Used for creating icons of a consistent size. + * @return the width + */ + int getWidth(); + + /** + * Gets the height of the shape's bounding box. + * Used for creating icons of a consistent size. + * @return the height + */ + int getHeight(); +} \ No newline at end of file diff --git a/hw4/src/HouseShape.java b/hw4/src/HouseShape.java new file mode 100644 index 0000000..15dbba9 --- /dev/null +++ b/hw4/src/HouseShape.java @@ -0,0 +1,68 @@ +import java.awt.Graphics2D; +import java.awt.geom.Path2D; +import java.awt.geom.Rectangle2D; + +/** + * A simple house shape composed of a body (rectangle), roof (triangle), + * door (rectangle), and window (rectangle). + */ +public class HouseShape implements CompositeShape { + private int width; // Width of the house body + private int height; // Height of the house body (excluding roof) + + public HouseShape() { + this(50, 40); // Default width and body height + } + + public HouseShape(int width, int height) { + this.width = width; + this.height = height; + } + + @Override + public int getWidth() { + return width; + } + + @Override + public int getHeight() { + // Total height = body height + roof height (approx 0.5 * width) + return height + (int) (width * 0.5); + } + + @Override + public void draw(Graphics2D g2) { + // Drawing relative to (0,0) - top-left of bounding box. + + // Body + double bodyY = width * 0.5; // Start body below roof peak + Rectangle2D.Double body = new Rectangle2D.Double(0, bodyY, width, height); + + // Roof (triangle using Path2D) + Path2D.Double roof = new Path2D.Double(); + roof.moveTo(0, bodyY); // Bottom-left corner of roof + roof.lineTo(width / 2.0, 0); // Peak of roof + roof.lineTo(width, bodyY); // Bottom-right corner of roof + // No need to close path for drawing outline + + // Door (small rectangle) + double doorWidth = width * 0.25; + double doorHeight = height * 0.5; + double doorX = (width - doorWidth) / 2.0; // Centered horizontally + double doorY = bodyY + height - doorHeight; // At the bottom of the body + Rectangle2D.Double door = new Rectangle2D.Double(doorX, doorY, doorWidth, doorHeight); + + // Window (small square rectangle) + double windowSize = width * 0.2; + double windowX = width * 0.15; // Positioned left of center + double windowY = bodyY + height * 0.2; // Positioned near top of body + Rectangle2D.Double window = new Rectangle2D.Double(windowX, windowY, windowSize, windowSize); + + + // Draw parts + g2.draw(body); + g2.draw(roof); + g2.draw(door); + g2.draw(window); + } +} diff --git a/hw4/src/ShapeDisplayer.java b/hw4/src/ShapeDisplayer.java index 4dd76b8..670c73f 100644 --- a/hw4/src/ShapeDisplayer.java +++ b/hw4/src/ShapeDisplayer.java @@ -1,71 +1,187 @@ import javax.swing.*; import java.awt.*; -import java.awt.geom.Rectangle2D; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.geom.AffineTransform; +import java.util.ArrayList; +import java.util.List; + +public class ShapeDisplayer extends JFrame { + + private static final int ICON_WIDTH = 50; + private static final int ICON_HEIGHT = 50; + + private DrawingPanel drawingPanel; + private CompositeShape currentShape = null; // The shape currently selected + + private JButton carButton; + private JButton snowmanButton; + private JButton houseButton; + private ShapeIcon carIcon; + private ShapeIcon snowmanIcon; + private ShapeIcon houseIcon; + + // Helper class to store drawn shapes and their positions + private static class PlacedShape { + final CompositeShape shape; + final Point position; // Top-left corner where the shape should be drawn + + PlacedShape(CompositeShape shape, Point position) { + this.shape = shape; + this.position = position; + } + } + + // Custom JPanel for drawing + private class DrawingPanel extends JPanel { + private final List drawnShapes = new ArrayList<>(); + + public DrawingPanel() { + setBackground(Color.WHITE); + setPreferredSize(new Dimension(600, 400)); // Set preferred size + + addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + if (currentShape != null) { + // Create a *new instance* for drawing, if shapes had state. + // For these stateless shapes, reusing is fine, but creating + // new instances is conceptually safer if shapes could change. + // Example: drawnShapes.add(new PlacedShape(createNewShapeInstance(currentShape), e.getPoint())); + + // Simpler approach for current stateless shapes: + drawnShapes.add(new PlacedShape(currentShape, e.getPoint())); + repaint(); // Trigger redraw + } + } + }); + } + + public void setCurrentShape(CompositeShape shape) { + currentShape = shape; + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2 = (Graphics2D) g.create(); // Work on a copy + + // Enable anti-aliasing for smoother graphics + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + + // Draw all the placed shapes + for (PlacedShape ps : drawnShapes) { + // Save the current transform state + AffineTransform savedTransform = g2.getTransform(); + + // Translate to the position where the mouse was clicked + g2.translate(ps.position.x, ps.position.y); + + // Draw the shape (its draw method assumes drawing at 0,0) + ps.shape.draw(g2); + + // Restore the original transform for the next shape + g2.setTransform(savedTransform); + } + + g2.dispose(); // Clean up the graphics copy + } + } + + + public ShapeDisplayer() { + super("Shape Displayer"); // Frame title + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + // --- Create Shapes --- + CompositeShape car = new CarShape(); + CompositeShape snowman = new SnowmanShape(); + CompositeShape house = new HouseShape(); // Your custom shape + + // --- Create Icons --- + carIcon = new ShapeIcon(car, ICON_WIDTH, ICON_HEIGHT); + snowmanIcon = new ShapeIcon(snowman, ICON_WIDTH, ICON_HEIGHT); + houseIcon = new ShapeIcon(house, ICON_WIDTH, ICON_HEIGHT); + + // --- Create Buttons --- + carButton = new JButton(carIcon); + snowmanButton = new JButton(snowmanIcon); + houseButton = new JButton(houseIcon); + + // Configure buttons (remove text, set margins if needed) + carButton.setMargin(new Insets(2, 2, 2, 2)); + snowmanButton.setMargin(new Insets(2, 2, 2, 2)); + houseButton.setMargin(new Insets(2, 2, 2, 2)); + + // --- Create Panels --- + drawingPanel = new DrawingPanel(); + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); // Align buttons left + buttonPanel.add(carButton); + buttonPanel.add(snowmanButton); + buttonPanel.add(houseButton); + + // --- Layout --- + setLayout(new BorderLayout()); + add(buttonPanel, BorderLayout.NORTH); + add(drawingPanel, BorderLayout.CENTER); + + // --- Add Action Listeners --- + carButton.addActionListener(e -> selectShape(car, carIcon)); + snowmanButton.addActionListener(e -> selectShape(snowman, snowmanIcon)); + houseButton.addActionListener(e -> selectShape(house, houseIcon)); + + + // Initially, no shape is selected + deselectAllIcons(); + + pack(); // Size the frame based on components + setLocationRelativeTo(null); // Center on screen + } + + // Method to handle shape selection + private void selectShape(CompositeShape shape, ShapeIcon icon) { + drawingPanel.setCurrentShape(shape); + deselectAllIcons(); // Clear previous selection outline + icon.setOutline(true); // Set outline on the selected icon + // Force repaint of the button containing the icon + if (icon == carIcon) carButton.repaint(); + else if (icon == snowmanIcon) snowmanButton.repaint(); + else if (icon == houseIcon) houseButton.repaint(); + + // Optional: Add a border to the button itself for clearer selection + // resetBorders(); // Reset borders on all buttons + // if (icon == carIcon) carButton.setBorder(BorderFactory.createLineBorder(Color.BLACK, 2)); + // else if (icon == snowManIcon) snowManButton.setBorder(BorderFactory.createLineBorder(Color.BLACK, 2)); + // else if (icon == houseIcon) houseButton.setBorder(BorderFactory.createLineBorder(Color.BLACK, 2)); + + } + + // Method to remove outline from all icons + private void deselectAllIcons() { + carIcon.setOutline(false); + snowmanIcon.setOutline(false); + houseIcon.setOutline(false); + // Repaint all buttons to remove outlines + carButton.repaint(); + snowmanButton.repaint(); + houseButton.repaint(); + } + + // Optional: Helper to reset button borders + // private void resetBorders() { + // Border defaultBorder = UIManager.getBorder("Button.border"); + // carButton.setBorder(defaultBorder); + // snowManButton.setBorder(defaultBorder); + // houseButton.setBorder(defaultBorder); + // } + -public class ShapeDisplayer { public static void main(String[] args) { - // Create and configure the main frame on the Event Dispatch Thread + // Run the GUI creation on the Event Dispatch Thread (EDT) SwingUtilities.invokeLater(() -> { - JFrame frame = new JFrame("Mancala"); - frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - frame.setSize(800, 400); - - // Create buttons panel - JPanel buttonPanel = new JPanel(); - buttonPanel.setLayout(new FlowLayout()); - - // Create placeholder shapes - JButton carButton = new JButton(new PlaceholderIcon(Color.RED)); - JButton snowmanButton = new JButton(new PlaceholderIcon(Color.BLUE)); - JButton compositeButton = new JButton(new PlaceholderIcon(Color.GREEN)); - - - // Add buttons to the panel - buttonPanel.add(carButton); - buttonPanel.add(snowmanButton); - buttonPanel.add(compositeButton); - - // Add buttons panel to the top of the frame - frame.add(buttonPanel, BorderLayout.NORTH); - - // Add drawing panel - JPanel drawingPanel = new JPanel(); - drawingPanel.setBackground(Color.WHITE); - frame.add(drawingPanel, BorderLayout.CENTER); - - // Center the frame on the screen and make it visible - frame.setLocationRelativeTo(null); - frame.setVisible(true); + ShapeDisplayer displayer = new ShapeDisplayer(); + displayer.setVisible(true); }); } } - -class PlaceholderIcon implements Icon { - private final Color color; - private static final int WIDTH = 60; - private static final int HEIGHT = 40; - - public PlaceholderIcon(Color color) { - this.color = color; - } - - @Override - public void paintIcon(Component c, Graphics g, int x, int y) { - Graphics2D g2 = (Graphics2D) g; - Rectangle2D.Double rect = new Rectangle2D.Double(x + 5, y + 5, WIDTH - 10, HEIGHT - 10); - g2.setColor(color); - g2.fill(rect); - g2.setColor(Color.BLACK); - g2.draw(rect); - } - - @Override - public int getIconWidth() { - return WIDTH; - } - - @Override - public int getIconHeight() { - return HEIGHT; - } -} diff --git a/hw4/src/ShapeIcon.java b/hw4/src/ShapeIcon.java new file mode 100644 index 0000000..259d5de --- /dev/null +++ b/hw4/src/ShapeIcon.java @@ -0,0 +1,92 @@ +import javax.swing.Icon; +import java.awt.Component; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.geom.AffineTransform; + +/** + * An icon that draws a CompositeShape. + */ +public class ShapeIcon implements Icon { + private final CompositeShape shape; + private final int width; + private final int height; + private boolean outline = false; // Flag to indicate if the icon should be outlined + + /** + * Constructs a ShapeIcon. + * @param shape the shape to draw + * @param width the desired width of the icon + * @param height the desired height of the icon + */ + public ShapeIcon(CompositeShape shape, int width, int height) { + this.shape = shape; + this.width = width; + this.height = height; + } + + /** + * Sets whether this icon should draw an outline (e.g., when selected). + * @param outline true to draw outline, false otherwise. + */ + public void setOutline(boolean outline) { + this.outline = outline; + } + + @Override + public int getIconWidth() { + return width; + } + + @Override + public int getIconHeight() { + return height; + } + + @Override + public void paintIcon(Component c, Graphics g, int x, int y) { + Graphics2D g2 = (Graphics2D) g.create(); // Work on a copy + + // Enable anti-aliasing for smoother graphics + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + // Translate to the icon's drawing position + g2.translate(x, y); + + // --- Centering and Scaling Logic --- + double shapeWidth = shape.getWidth(); + double shapeHeight = shape.getHeight(); + + // Calculate scaling factor to fit shape within icon bounds + double scaleX = (shapeWidth == 0) ? 1 : (double) width / shapeWidth; + double scaleY = (shapeHeight == 0) ? 1 : (double) height / shapeHeight; + double scale = Math.min(scaleX, scaleY) * 0.8; // Use 80% of space for padding + + // Calculate translation to center the scaled shape + double dx = (width - shapeWidth * scale) / 2.0; + double dy = (height - shapeHeight * scale) / 2.0; + + // Apply centering translation and scaling + AffineTransform originalTransform = g2.getTransform(); + g2.translate(dx, dy); + g2.scale(scale, scale); + // --- End Centering and Scaling --- + + // Draw the shape + shape.draw(g2); + + // Restore original transform before drawing outline (if any) + g2.setTransform(originalTransform); + + // Draw outline if selected + if (outline) { + // Simple rectangle outline around the icon area + g2.setColor(java.awt.Color.GRAY); + g2.drawRect(0, 0, width - 1, height - 1); + g2.drawRect(1, 1, width - 3, height - 3); // Inner outline + } + + g2.dispose(); // Clean up the graphics copy + } +} diff --git a/hw4/src/SnowmanShape.java b/hw4/src/SnowmanShape.java new file mode 100644 index 0000000..3f3e89f --- /dev/null +++ b/hw4/src/SnowmanShape.java @@ -0,0 +1,60 @@ +import java.awt.Graphics2D; +import java.awt.geom.Ellipse2D; + +/** + * A snowman shape composed of three circles. + */ +public class SnowmanShape implements CompositeShape { + private int width; // Base diameter of the bottom circle + + public SnowmanShape() { + this(40); // Default width + } + + public SnowmanShape(int width) { + this.width = width; + } + + @Override + public int getWidth() { + return width; + } + + @Override + public int getHeight() { + // Total height is approx sum of diameters: w + 0.75w + 0.5w + return (int) (width + width * 0.75 + width * 0.5); + } + + @Override + public void draw(Graphics2D g2) { + // All drawing relative to (0,0) which represents the top-left + // of the bounding box for the *icon*. The actual drawing will be centered. + + // Calculate diameters + double bottomDiameter = width; + double middleDiameter = width * 0.75; + double topDiameter = width * 0.5; + + // Calculate positions (y-coordinates are offset from the top) + double bottomY = topDiameter + middleDiameter; + double middleY = topDiameter; + double topY = 0; + + // Center horizontally (x-coordinates) + double bottomX = (width - bottomDiameter) / 2.0; + double middleX = (width - middleDiameter) / 2.0; + double topX = (width - topDiameter) / 2.0; + + + // Create circles + Ellipse2D.Double bottomCircle = new Ellipse2D.Double(bottomX, bottomY, bottomDiameter, bottomDiameter); + Ellipse2D.Double middleCircle = new Ellipse2D.Double(middleX, middleY, middleDiameter, middleDiameter); + Ellipse2D.Double topCircle = new Ellipse2D.Double(topX, topY, topDiameter, topDiameter); + + // Draw circles + g2.draw(bottomCircle); + g2.draw(middleCircle); + g2.draw(topCircle); + } +}