2 November 2020

A Little Polymorphic Fun

Download these two files; they are the latest versions.

Marketing has called! They want circles, and your quarterly bonus is riding on it. What are we to do? We really want that Tahitian vacation!

So, let's make a Circle class. Just copy the Square class and make a simple modifiction in the draw method.


import javafx.scene.paint.Color;
import javafx.scene.canvas.GraphicsContext;
public class Circle 
{
    private final Color color;
    private final double xCenter;
    private final double yCenter;
    private final double size;
    public Circle(Color color, double xCenter, double yCenter, double size)
    {
        this.color = color;
        this.xCenter = xCenter;
        this.yCenter = yCenter;
        this.size = size;
    }
    public void draw(GraphicsContext pen)
    {
        //draw myself
        pen.setFill(color);
        pen.fillOval(xCenter - size/2, yCenter - size/2, size, size);
    }
}

Houston, we have a problem. Our Coordianted class only uses Squares. What do we do? First of all, we glue the Square and Circle classes together by creating an interface.


import javafx.scene.canvas.GraphicsContext;
public interface Drawable
{
    public void draw(GraphicsContext g);
}

Now accept the contract.


import javafx.scene.paint.Color;
import javafx.scene.canvas.GraphicsContext;
public class Square implements Drawable
{
    private final Color color;
    private final double xCenter;
    private final double yCenter;
    private final double size;
    public Square(Color color, double xCenter, double yCenter, double size)
    {
        this.color = color;
        this.xCenter = xCenter;
        this.yCenter = yCenter;
        this.size = size;
    }
    public void draw(GraphicsContext pen)
    {
        //draw myself
        pen.setFill(color);
        pen.fillRect(xCenter - size/2, yCenter - size/2, size, size);
    }
}

import javafx.scene.paint.Color;
import javafx.scene.canvas.GraphicsContext;
public class Circle implements Drawable
{
    private final Color color;
    private final double xCenter;
    private final double yCenter;
    private final double size;
    public Circle(Color color, double xCenter, double yCenter, double size)
    {
        this.color = color;
        this.xCenter = xCenter;
        this.yCenter = yCenter;
        this.size = size;
    }
    public void draw(GraphicsContext pen)
    {
        //draw myself
        pen.setFill(color);
        pen.fillOval(xCenter - size/2, yCenter - size/2, size, size);
    }
}

Now we are in better "shape."

In our Coordinated class, we have an array list of squares. We must modify it to be an array list of Drawables. We also changed the name of squares to shapes.


/**************************************************
*   Author: Morrison
*   Date:  26 Oct 202020
**************************************************/
import javafx.application.Application;
import javafx.application.Platform;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.MenuBar;
import javafx.scene.control.ColorPicker;
import javafx.scene.paint.Color;
import java.util.ArrayList;

public class Coordinated extends Application
{
    private Stage primary;
    private Canvas canvas;
    private GraphicsContext pen;
    private Color currentColor;
    private Color bgColor;
    private ArrayList<Drawable> shapes;
    private ArrayList<Drawable> redoBoneyard;
    public Coordinated()
    {
        canvas = new Canvas(800,600);
        pen = canvas.getGraphicsContext2D();
        currentColor = Color.BLACK;
        shapes = new ArrayList<>();
        redoBoneyard = new ArrayList<>();
        bgColor = Color.WHITE;
    }
    
    @Override
    public void init()
    {
    }

    @Override
    public void start(Stage primary)
    {
        this.primary = primary;
        BorderPane bp = new BorderPane();
        bp.setCenter(canvas);
        refresh();
        bp.setTop(buildMenus());
        canvas.setOnMouseClicked( e ->
        {
            s = new Square(currentColor, e.getX(), e.getY(), 100);
            shapes.add(s);
            s.draw(pen);
        });
        primary.setScene(new Scene(bp));
        primary.show();
    }
    private MenuBar buildMenus()
    {
        MenuBar mbar =  new MenuBar();
        Menu colorMenu = new Menu("Color");
        Menu backgroundMenu = new Menu("Background");
        Menu undoRedoMenu = new Menu("Undo/Redo");

        MenuItem undoItem = new MenuItem("Undo");
        MenuItem redoItem = new MenuItem("Redo");
        undoRedoMenu.getItems().addAll(undoItem, redoItem);
        undoItem.setOnAction( e ->
        {
            if(!shapes.isEmpty())
            {
                redoBoneyard.add(shapes.remove(shapes.size() - 1));
                refresh();
            }
        });
        redoItem.setOnAction( e ->
        {
            if(!redoBoneyard.isEmpty())
            {
                shapes.add(redoBoneyard.remove(redoBoneyard.size() - 1));
                refresh();
            }
        });
        mbar.getMenus().addAll(undoRedoMenu, backgroundMenu, colorMenu,
            shapeMenu);
        colorMenu.getItems().addAll(
            new ColorMenuItem(Color.RED, "red"),
            new ColorMenuItem(Color.BLUE, "blue"),
            new ColorMenuItem(Color.GREEN, "green"),
            new ColorMenuItem(Color.ORANGE, "orange"),
            new ColorMenuItem(Color.rgb(0, 0x1a, 0x57), "dook")
            );
        MenuItem customColor = new MenuItem("custom...");
        colorMenu.getItems().add(customColor);
        customColor.setOnAction( choose -> 
        {
            Stage popup = new Stage();
            Pane p = new Pane();
            popup.setScene(new Scene(p));
            ColorPicker cp = new ColorPicker();
            p.getChildren().add(cp);
            popup.show();
            cp.setOnAction( e ->
            {
                currentColor = cp.getValue();
                popup.close();
            });
        });

        backgroundMenu.getItems().addAll(
            new BgMenuItem(Color.PINK, "pink"),
            new BgMenuItem(Color.LIGHTBLUE, "light blue"),
            new BgMenuItem(Color.WHITE, "white"),
            new BgMenuItem(Color.BLACK, "black")
            );
        return mbar;
    }
    public void refresh()
    {
        pen.setFill(bgColor);
        pen.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
        for(Drawable s: shapes)
        {
            s.draw(pen);
        }
    }

    @Override
    public void stop()
    {
    }
    public static void main(String[] args)
    {
        Application.launch();
    }
    /********************** Aliens *************************/
    class ColorMenuItem extends MenuItem
    {
        private final Color color;
        public ColorMenuItem(Color color, String name)
        {
            super(name);
            this.color = color;
            setOnAction( e -> 
            {
                currentColor = color;
            });
        }

    }
    class BgMenuItem extends MenuItem
    {
        private final Color color;
        public BgMenuItem(Color color, String name)
        {
            super(name);
            this.color = color;
            setOnAction( e -> 
            {
                bgColor = color;
                refresh();
            });
        }
    }
}

Inside of the "Aliens" section, we are going to add a new item, an enum


enum ShapeType{SQUARE, CIRCLE};