import javafx.application.Application; import javafx.stage.Stage; import javafx.scene.Scene; import javafx.scene.layout.Pane; import javafx.scene.layout.BorderPane; import javafx.scene.control.Menu; import javafx.scene.control.MenuBar; import javafx.scene.control.MenuItem; import javafx.scene.control.CheckMenuItem; import javafx.scene.control.RadioMenuItem; import javafx.scene.control.ToggleGroup; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.input.MouseEvent; import javafx.scene.paint.Color; import javafx.geometry.Point2D; import java.util.ArrayList; /** * This program has a drawing surface on which the user can * sketch curves, with menus to control the curve color * and background color. The user can turn on a "symmetry" * option; this makes the program draw horizontal and * vertical reflections of the user's curves. * * (The real point of this example is to demonstrate ArrayList.) */ public class SimplePaint2 extends Application { public static void main(String[] args) { launch(args); } //-------------------------------------------------------------------- /** * An object of type CurveData represents the data required to redraw one * of the curves that have been sketched by the user. */ private static class CurveData { Color color; // The color of the curve. boolean symmetric; // Are horizontal and vertical reflections also drawn? ArrayList points; // The points on the curve. } private ArrayList curves; // A list of all curves in the picture. private Canvas canvas; // The canvas on which curves are drawn. private GraphicsContext g; // A graphics context for drawing on the canvas private Color backgroundColor; // The current background color of the canvas private Color currentColor; // When a curve is created, its color is taken // from this variable. The value is changed // using commands in the "Color" menu. private boolean useSymmetry; // When a curve is created, its "symmetric" // property is copied from this variable. Its // value is set by the "Use Symmetry" command in // the "Control" menu. /** * Sets up the GUI with a canvas for drawing and a menu bar. * Also initializes global variables, and installs mouse event * handlers to respond when the user drags the mouse on the canvas. */ public void start(Stage stage) { currentColor = Color.BLACK; backgroundColor = Color.WHITE; curves = new ArrayList(); canvas = new Canvas(600,600); g = canvas.getGraphicsContext2D(); redraw(); // just fills canvas with background color Pane canvasHolder = new Pane(canvas); // for adding a border around the canvas canvasHolder.setStyle("-fx-border-color:darkgray; -fx-border-width:3px"); canvas.relocate(3,3); // Since the holder is a Pane, we have to set the // canvas location manually, to allow for the // border. Otherwise, canvas would be at (0,0). canvas.setOnMousePressed( e -> mousePressed(e) ); canvas.setOnMouseDragged( e -> mouseDragged(e) ); canvas.setOnMouseReleased( e -> mouseReleased(e) ); BorderPane root = new BorderPane(); root.setCenter( canvasHolder ); root.setTop( createMenuBar() ); Scene scene = new Scene(root); stage.setScene(scene); stage.setTitle("Draw Some Curves!"); stage.setResizable(false); stage.show(); } // end start() /** * Fills the panel with the current background color and draws all the * curves that have been sketched by the user. This is called when * the picture has to be completely redrawn such as when the * background color changes or when an Undo command is applied. */ private void redraw() { g.setFill(backgroundColor); g.fillRect(0,0,canvas.getWidth(),canvas.getHeight()); for ( CurveData curve : curves) { g.setStroke(curve.color); for (int i = 1; i < curve.points.size(); i++) { // Draw a line segment from point number i-1 to point number i. double x1 = curve.points.get(i-1).getX(); double y1 = curve.points.get(i-1).getY(); double x2 = curve.points.get(i).getX(); double y2 = curve.points.get(i).getY(); drawSegment(curve.symmetric,x1,y1,x2,y2); } } } // end redraw() /** * Strokes a line segment, using the current drawing color from (x1,y1) to (x2,y2). * If symmetric is true, also draws the horizontal and vertical reflections * of that segment. This is called by redraw() and also when the mouse moves * during a drag operation on the canvas */ private void drawSegment(boolean symmetric, double x1, double y1, double x2, double y2) { g.strokeLine(x1,y1,x2,y2); if (symmetric) { // Also draw the horizontal and vertical reflections // of the line segment. double w = canvas.getWidth(); double h = canvas.getHeight(); g.strokeLine(w-x1,y1,w-x2,y2); g.strokeLine(x1,h-y1,x2,h-y2); g.strokeLine(w-x1,h-y1,w-x2,h-y2); } } //------------------- implement mouse dragging ------------------------------- private CurveData currentCurve; // During a drag, the curve that is being drawn private boolean dragging; // Is a drag in progress? /** * Called when the user presses the mouse on the canvas. A new CurveData object * is created to hold the points on the curve that the user is drawing. * and the point where the mouse was pressed is added as the first point on * the curve. The color and symmetry property of the curve are taken from the * current values of global variables currentColor and useSymmetry. The * new curve is not actually added to the list of curves until the mouse is * released. */ private void mousePressed(MouseEvent evt) { if (dragging) return; dragging = true; currentCurve = new CurveData(); currentCurve.color = currentColor; currentCurve.symmetric = useSymmetry; currentCurve.points = new ArrayList(); currentCurve.points.add( new Point2D(evt.getX()+0.5, evt.getY()+0.5) ); g.setStroke(currentColor); // set currentColor to be used for drawing this curve } /** * Called when the mouse moves during a drag operation. Adds a point to * the curve and draws a line segment from the previous point to the current * point. */ private void mouseDragged(MouseEvent evt) { if (!dragging) return; Point2D currentPoint = new Point2D( evt.getX()+0.5, evt.getY()+0.5 ); Point2D prevPoint = currentCurve.points.get(currentCurve.points.size() - 1); currentCurve.points.add( currentPoint ); drawSegment(useSymmetry, prevPoint.getX(), prevPoint.getY(), currentPoint.getX(), currentPoint.getY()); } /** * Called when the user releases the mouse. The current curve is added to * the list of curves, but only if the number of points is at least 2. * (If there is only one point, it means that the user didn't move the * mouse at all, and no curve was actually drawn. In that case, the * currentCurve object should simply be discarded.) */ private void mouseReleased(MouseEvent evt) { if (!dragging) return; dragging = false; if (currentCurve.points.size() > 1) curves.add(currentCurve); currentCurve = null; } //------------------------ implement menus ----------------------------- private static final String[] colorNames = { // List of available color names for the Color and BackgroudColor menus . "Black", "White", "Red", "Green", "Blue", "Cyan", "Magenta", "Yellow", "Gray", "Brown", "Purple", "Pink", "Orange" }; private static final Color[] colors = { // List of Colors corresponding to the names in the colorNames array. Color.BLACK, Color.WHITE, Color.RED, Color.GREEN, Color.BLUE, Color.CYAN, Color.MAGENTA, Color.YELLOW, Color.GRAY, Color.BROWN, Color.PURPLE, Color.PINK, Color.ORANGE }; /** * Creates a menu bar for use with this panel. It contains * three menus: "Control", "Color", and "BackgroundColor". */ public MenuBar createMenuBar() { /* Create the menu bar object */ MenuBar menuBar = new MenuBar(); /* Create the menus and add them to the menu bar. */ Menu controlMenu = new Menu("Control"); Menu colorMenu = new Menu("Color"); Menu bgColorMenu = new Menu("BackgroundColor"); menuBar.getMenus().addAll(controlMenu,colorMenu,bgColorMenu); /* Add commands to the "Control" menu. It contains an Undo * command that will remove the most recently drawn curve * from the list of curves; a "Clear" command that removes * all the curves that have been drawn; and a "Use Symmetry" * checkbox that determines whether symmetry should be used. */ MenuItem undo = new MenuItem("Undo"); undo.setOnAction( e -> { if (curves.size() > 0) { curves.remove( curves.size() - 1); redraw(); // Redraw without the curve that has been removed. } }); MenuItem clear = new MenuItem("Clear"); clear.setOnAction( e -> { curves = new ArrayList(); redraw(); // Redraw with no curves shown. }); CheckMenuItem sym = new CheckMenuItem("Use Symmetry"); sym.setOnAction( e -> useSymmetry = sym.isSelected() ); controlMenu.getItems().addAll(undo,clear,sym); /* Add commands to the "Color" menu. The menu contains commands for * setting the current drawing color. When the user chooses one of these * commands, it has no immediate effect on the drawing. It just sets * the color that will be used for future drawing. */ ToggleGroup colorGroup = new ToggleGroup(); for (int i = 0; i < colorNames.length; i++) { RadioMenuItem item = new RadioMenuItem(colorNames[i]); colorMenu.getItems().add(item); item.setUserData(Integer.valueOf(i)); item.setToggleGroup(colorGroup); if (i == 0) { item.setSelected(true); } } colorGroup.selectedToggleProperty().addListener( (e,oldVal,newVal) -> { if (newVal != null) { // When the user selects a new RadioMenuItem from the group, // the selectedToggle property changes twice, once to null, // then to the newly selected RadioMenuItem. // The "userData" property of a Node is a place where // a program can stash data associated with the node // that will be needed later in the program. It can be // any object. Here, I use it to stash the color number // associated with the RadioMenuItem so I know which // color to use. The value is an Integer, which is // automatically "unboxed" to an int when used here as // an array index. currentColor = colors[ (Integer)newVal.getUserData() ]; } }); /* Add commands to the "BackgroundColor" menu. The menu contains commands * for setting the background color of the panel. When the user chooses * one of these commands, the panel is immediately redrawn with the new * background color. Any curves that have been drawn are still there. */ ToggleGroup bgGroup = new ToggleGroup(); for (int i = 0; i < colorNames.length; i++) { RadioMenuItem item = new RadioMenuItem(colorNames[i]); bgColorMenu.getItems().add(item); item.setUserData(Integer.valueOf(i)); item.setToggleGroup(bgGroup); if (i == 1) { item.setSelected(true); } } bgGroup.selectedToggleProperty().addListener( (e,oldVal,newVal) -> { if (newVal != null) { backgroundColor = colors[ (Integer)newVal.getUserData() ]; redraw(); // picture has to be redrawn with new background color } }); /* Return the menu bar that has been constructed. */ return menuBar; } // end createMenuBar } // end class SimplePaint2