Early Handin: Sunday, December 8, 11:59 PM ET
Regular Handin: Tuesday, December 10, 11:59 PM ET
Late Handin: Thursday, December 12, 5:00 PM ET
Silly Premise
Collaboration Policy Reminder
Main Concepts
New Concepts Covered
Helpful Resources
Important Prelude READ THIS!
Grading
Installing Stencil Code
About the Stencil
Coding Incrementally
Code Checkpoint
Minimum Functionality Requirements
Full Functionality Requirements
Bells and Whistles
Sketchy Specs
Using Point 2Ds
Free draw with the cursor
Create at least two types of Shape
Specify the color of shapes
Select any created shape
Move/translate the selected shape
Rotate the selected shape
Resize the selected shape
Delete the selected shape
Raise/Lower shapes
Command Pattern
Undo and redo any action executed within the program
Save and load the entire state of your drawing
MouseEvents
Packages
SelectOptions and Enums
As CS15 draws to a close, you may find yourself thinking: “Self, this has been an entertaining and educational class, but how does all this relate to the real world? And why do all the projects have such boring names?” Well, all of your questions have been answered in the form of Sketchy! Sketchy is the final project with flair. Sketchy is the final project that allows you to write a program that is more than entertainment - it has some real functionality. Sketchy is the final project that will change your life forever. What is Sketchy? Sketchy is the world’s latest and greatest graphics editing program. It lets you draw to your heart’s content, create different kinds of shapes on a canvas and manipulate them in various ways. Not everyone is perfect, so Sketchy has undo and redo capabilities. And since no program worth its salt would force you to create a masterpiece in one session, Sketchy can save and load your works of art!
If you ever have questions about the collaboration policy, refer to the collaboration policy or ask a TA!
Note: The usage of any artificial intelligence technologies, except those explicitly endorsed by CS15, is strictly prohibited. Because of TAs reading your code and a software package we use called MOSS (Measure of Software Similarity), illegal collaboration is easily detected in CS15.
Sure, this talk about the features of the program is all fine and dandy, but how will you implement all of this? We’ve broken up the project into five main stages:
Make sure to watch the demo before reading the rest of the handout! The handout focuses on how to implement the functionality seen in the demo, and being able to mentally visualize that functionality will help a lot when reading.
In our demo, shapes are selected by clicking on them with the “Select Shape” radio button selected. Here’s how the interactions work:
You’re free to implement Sketchy with whatever controls you’d like as long as it makes sense (using different keys, or middle/right mouse button, for example). Just make sure you tell us in your comments and README!
Note that you do not have to complete these steps in this order, and that some steps are possible without doing the tasks listed before it in these suggested steps. For example, you will obviously have to be able to get shapes to appear before working on rotation, translation, deletion, etc., but you do not need to know how to translate a shape before being able to rotate it. If you find yourself in a pinch, you can try another step! If you are waiting in line for hours for help with rotation, try tinkering with undo/redo or saving and loading!
We cannot emphasize this enough! Sketchy is a large project, so you have to keep working on other sections of the project even as you debug one aspect. This isn’t to say you should accumulate lots of bugs and leave them for the end, but if you get stuck on something and can’t seem to get unstuck, try to shift gears and work on other areas of the project.
As with Tetris, you should start early and code incrementally. Please come to Conceptual Hours with any questions about design and implementation. Also, you’re required to come to Conceptual Hours to get your code checkpoint checked off. Come to Debugging Hours only with bugs that you can't solve on your own. The IntelliJ debugger, printlines, stack trace, and other resources we’ve provided will help you fix most problems that you face (you can refer back to the Debugging Lab and the Debugging Cheat Sheet for more information).
If you go to TA hours seeking debugging help, be prepared to show the TA extensive debugging efforts. If the TA feels that you haven’t spent enough time trying to solve the bug, they have the right to refuse you. It is in your best interest to remove yourself from the list if you resolve your bug or feel as though you haven’t debugged sufficiently, if you don’t you will get turned away and have to wait an hour to sign up again (as per the TA hours policy).
Read the ENTIRE handout from start to finish. Refer back to it when you start a new section, and everytime you encounter a bug. This handout has critical information that is easy to miss when skimmed! Remember that we make these handouts to help you tackle obstacles we know you’ll face!
The grade for this assignment will be determined by functionality (50%), design (35%), and style (15%).
These are the features that you must implement properly in order to receive full credit on the assignment. The user must be able to:
Read on to the design portion of this handout for the exact specs for these requirements!
Click here to get the stencil from Github. Refer to the CS15 GitHub Guide for help with GitHub and GitHub Classroom.
Once you’ve cloned your personal repository from GitHub, you’ll need to rename the folder from sketchy-<yourGitHubLogin></yourGitHubLogin> to just sketchy. You will have issues running your code until you make the change.
As with Tetris and DoodleJump, we will only be giving you a few files in the stencil.
You’ll also see that the commands and shapes packages contain an empty file called .gitkeep. This is just here to inform git that we want this folder to be pushed to GitHub as well - git will not otherwise add empty directories. Make sure you delete this file once you’ve added something to that package!
After you’ve watched the demo, thoroughly read this handout (especially the Design Details) to make sure you understand the project design. Once you’re ready, start coding! It is important to code incrementally, meaning you completely accomplish one logical task before moving on to the next one.
Suggested Order for Incremental Coding
Sketchy is a large project. You will be writing more classes and more lines of code than you have for any other project in CS15. This means that it is especially important to code Sketchy incrementally! If you do not, you will be stuck with terrifying JavaFX stack traces that you may have no idea how to debug. Here’s one way to code Sketchy incrementally:
Step 1. Run the stencil code.
Step 2. Finish setting up the GUI - arrange your Panes on the screen, add all of the necessary Buttons, Labels, and ColorPicker for controls, get everything looking how you want it to.
Step 3. Make one type of shape show up in response to a mouse click. Consider giving shapes a default initial size at this stage since you haven’t implemented resize yet!
Step 4. Make other types of shape show up.
Step 5. Implement free draw with the cursor.
Step 6. Allow the user to select a shape (don’t forget about the contains method!).
Step 7. Use the ColorPicker to select a color and fill the selected shape with that color.
Step 8. Delete the selected shape.
Step 9. Translate the selected shape.
Step 10. Rotate the selected shape.
Step 11. Resize the selected shape, then update shape creation to use your resize method.
Step 12. Raise and lower shapes (layering).
Step 13. Undo/redo shape creation, color changing, raise, and lower.
Step 14. Undo/redo everything else
Step 15. Save/load drawings.
Step 16. Also remember to create a README (follow the guidelines in the README section).
This project will have a checkpoint that you’ll need to get checked off at conceptual hours by Sunday, November 24th. Sketchy is a large project, so we want to make sure you’re on track. Here is what you need to have completed by then:
The checkpoint is less than halfway through the project and is there to make sure that you get started. The later parts of this project can be more time consuming than the first parts so please make sure you keep working on it after you meet the checkpoint!
Your Sketchy will have to do the following things in order for you to meet minimum functionality (REMINDER: you have to achieve minimum functionality on all projects to pass the course, and since this is the final project, you cannot hand in again after the late handin deadline!)
MF Policy Summary: In order to pass CS15, you will have to meet minimum functionality requirements for all projects. MF requirements are not the same as the requirements for full credit on the project. You should attempt the full requirements on every project to keep pace with the course material. An ‘A’ project would meet all of the requirements on the handout and have good design and code style.
To meet MF for Sketchy:
Beyond the minimum functionality requirements, the rest of the functionality grade will depend on the following criteria:
We’ll be happy to reward plenty of extra credit to any Sketchy that goes above and beyond! Here are some ideas:
Remember that you should make sure that you have a fully functional program before working on extra credit. Remember, from the Course Missive:
“Extra credit is only to be done after the original assignment has been fully completed - if you have not met the requirements, you will not receive extra credit. Extra credit may not redefine the original assignment. Additionally, priority is given at TA hours to students trying to complete minimum functionality. Do not expect to be seen for extra credit questions at busy times.”
Here are some further specs to help you meet the assignment requirements listed above!
In this project, you’ll be using many (x, y) points to set and keep track of locations of mouse presses and shapes. Writing many accessors and mutators for both x and y locations of objects can become very repetitive. If only we could keep track of both x and y locations in one variable… Good news, Point2Ds
(javafx.geometry.Point2D
) can do just that! Point2Ds
are wrapper classes around an (x, y) coordinate location. To instantiate a Point2D
, you’d do
Point2D point = new Point2D(<x location>, <y location>);
Also, when you want to access either the x or y coordinate specifically you can use
point.getX();
point.getY();
Check out the Javadocs for more information! You’re not required to use Point2Ds, but they might prove to be helpful in keeping your code concise!
In order to let the user draw, you’ll need an object-oriented way of representing curved lines. The javafx.scene.shape.Polyline
class stores an array of doubles (alternating x, y) representing points, and draws a line connecting all of those points on the pane. So a Polyline
with the array {2, 3, 4, 5}
would look like one straight line segment from the point (2, 3)
to the point (4, 5)
. It’s possible to modify the double array of an existing Polyline
to add new points - you can access the array with the getPoints()
method. You’ll want to make your own CurvedLine
class that stores a Polyline
instance, so that we can add our own functionality. This class will be a wrapper class, similar to your doodle class in DoodleJump
or your Square
/Rectangle
class in Tetris (if you had one). You’ll probably want to write a method that adds a point to your CurvedLine
’s underlying Polyline
. Now that we have a way to represent **CurvedLine
**s, we need to add user interaction. The Pane that represents your “canvas” will have a handler (or handlers) that fires when the user interacts with it through the mouse. You’ll probably want to do something like the following:
CurvedLine
and store a reference to it - i.e., in a variable (think about what type of variable it should be). You will need a reference to this particular instance of the CurvedLine
so that you know where additional points must be added to.CurvedLine
will only contain two doubles, referring to the x and y position of the mouse press.Polyline
graphically to the pane it is contained in.Polyline
’s list of doubles, passing in the current x and y position of the mouse.Polyline
instance will maintain the list of its points, so every point you add should continue to appear graphically as well.In Sketchy, you are required to implement two or more types of shapes which the user can add to the Pane
representing your canvas. You will have a group of RadioButtons
(see the JavaFX documentation) that allow the user to select what kind of shape they want to draw (see the demo). If a shape creation button is selected, then once the user clicks upon the canvas Pane
, the selected shape is created at the position of the click. The user will drag the mouse to set the shape’s initial size, just as they would resize an existing shape. You should not just create a shape with some constant size. Use classes in the JavaFX sShape
package to model shapes as you have done in previous projects. Consider, however, that you will want some functionality in addition to the basics provided by the JavaFX Shape class
, so creating your own classes to represent shapes would be a good idea. We recommend creating a separate SketchyShape
interface and implementing it in specific shape classes that contain JavaFX Shapes
.
The basic requirement for color selection in Sketchy is to use the javafx.scene.control.ColorPicker
(see Javadocs), though you are more than welcome to use some other method of selecting colors. The ColorPicker
has the getValue()
method, which will allow you to determine the currently selected color value. Once the user has selected a color, anything added to the canvas must appear in this color. Additionally, you should be able to change the color of your shapes on the Pane to whatever color is indicated by the ColorPicker
by selecting the shape and clicking the Fill button.
You are not required to be able to change the color of the lines after they are drawn, though this can be done for extra credit.
You should be able to select any of your shapes by clicking on it. A shape should indicate that it is selected by drawing a border (hint: stroke!) around it. Your lines are not required to be selectable. Once a shape has been selected, the shape manipulation buttons should act on the selected shape. If you click off the shape or start drawing a shape or line, then the shape should become deselected and the border around it should disappear. Note that in the case of overlapping shapes, the shape on top (and only that shape) should be selected. There are several strange edge cases for shape selection - think through these!
JavaFX Nodes
have a built-in contains()
method which you can use to check whether a mouse click is within the original bounds of a shape. However, this method does not take rotation into account. When a shape is rotated, its bounds change, and a point that was contained in its original, unrotated bounds may not be contained in the rotated shape. In order to correctly use this method, then, we’ll have to rotate the given point about the shape’s center by the same degree the shape is rotated, and check if the Node
contains the rotated point.
But how do we rotate a point around another point? We’re providing you with pseudocode for this method. The method takes in a pointToRotate
, and rotates the point by angle degrees around the point rotateAround
.
You should translate the selected shape as the mouse is dragged. You’ll want to create a translate(...)
method that takes in two points – one “previous” mouse point, and one “current” mouse point. Think about how you could use Point2Ds
here to make your code more concise! In your translation method, you’ll determine the difference in x (dx) and difference in y (dy) between the two points and adjust the shape’s location by those values.
Note: do not use the setTranslateX
and setTranslateY
methods. This will make your life far, far more difficult than it needs to be.
Just like you did with translation, you’ll want to create a rotate(...)
method that takes in two points, the previous mouse point prev and the current point curr. Calculate the angle between the two points about the center of the shape and then increment or decrement the shape’s rotation by this angle. You’ll find that Java’s Math
class has some very helpful static methods. The angle between two points is given by:
Note: Watch out for conversion between degrees and radians!
Resizing the selected shape requires again writing a method that takes in the current mouse point curr
. In our demo, holding down SHIFT and dragging the mouse will resize the selected shape. For Full Functionality your shapes should resize in the same way as the demo. The center of the shape should be fixed, and the shape should resize from the center. Notice that resizing an unrotated shape is relatively straightforward. We just increase (or decrease) the shape’s width and height by dx and dy while maintaining its center location. How will we handle resizing the shape once it has been rotated?
Similarly to what we had to do for the contains method above, we have to rotate the input curr
about the shape’s center before we can use them to calculate dx and dy. To understand the reasons behind this, consider this example:
You have a Rectangle
that’s rotated 90 degrees, and you drag the mouse vertically to make the shape bigger in the y direction. You might think that you could just update the shape’s height by dy, but because the shape is turned on its side, changing the shape’s height actually makes it longer horizontally. If we first rotate the input points by 90 degrees, we will get new points that differ by the same amount, but in the x direction instead. dx corresponds to change in width, so we update the shape’s width accordingly, which actually makes it bigger vertically, giving us the desired result.
Now we can write our resize(...)
method to take in one point, curr point which is the mouse’s current location. The resize method will work as follows:
Once a shape is drawn, you should provide the option for the user to remove it from the canvas. Your shapes will be stored in some sort of data structure(s), so consider how to properly remove a specific element. How will you remove a shape from the data structure(s) and ensure that it is no longer displayed on the canvas?
Remember that when you delete a shape and then undo that action, you will want that shape to preserve the layering it had before it was deleted. Graphical layering is related to the order in which nodes are added to a Pane - think about how you might keep track of which index in the ArrayList
of a pane’s children a particular shape was added to.
When two shapes or lines overlap, whichever is added to the Pane’s List
of children last is rendered on top.
Your “raise” button will bring a shape one layer forward such that it is graphically above the shape/line that was previously above it. Similarly, your “lower” button should move a shape one layer backwards such that it appears graphically under the shape/line that was previously under it. Consider how you will accomplish this change both graphically (on the Pane) and logically (in your data structures).
Your data structures should store shapes and lines in the same order that the pane’s list of children does. This is important for undo/redoing this command (and undoing deletion) and for saving/loading. When changing shape layering, you’ll need to change:
You can get an object’s index in an ArrayList
using the list’s indexOf
method. Make sure the object you’re passing in is actually in the list!
How can we undo raising a shape? Lower it! (and vice versa). To undo deletion, however, you will need to know the indices the shape previously had in all relevant data structures in order to perform an accurate undo.
JavaFX panes’ lists of children have some strange rules that mean you cannot easily swap two elements in the list. Specifically, the list cannot have two of the same Node
and cannot have null elements. Therefore, the best way to change a Node
’s index in the list is to figure out the index it needs to move to, remove the Node
, and then add it back in at the proper index. Think carefully about this and consider drawing out a few examples. You might also find that this remove-and-then-add method is also the easiest way to change the index of an object in your other data structures.
Lines add a degree of complexity to how we handle layers. If you had a data structure that stored all your shapes in the right order, adjacent shapes in that data structure may not be adjacent in the Pane
’s list of children - there may be lines in between! Thus, in order to correctly change the index of a shape in its data structure, we need to consider its index in the Pane
’s list of children as well as the index of the next/previous shape in the list of children. This can become quite complicated to deal with, so we’ve given you pseudocode to handle this situation. If this is confusing, please come to conceptual hours to talk through it!
The following pseudocode is to calculate the change in shape index when raising a shape and only needs minor changes to deal with lowering! The crux of it is as follows: if a shape is next to another shape in the shapes data structure and the Pane’s list of children, then it can be moved in both. If a shape is next to another shape in the shapes data structure but not next to the other shape in the Pane
’s list of children, the difference between their indices is greater than 1 , and there are line(s) between the two shapes! So it should only be moved in the Pane
’s list of children.
The Command Pattern is used to model commands and actions. Modeling commands may seem pretty abstract at first, but that’s what CS15 is all about: abstraction! Using polymorphism, you’ll abstract these commands into an interface or abstract superclass, and then define a class for every command you want to model.
Think of commands as actions or things to be done. In Sketchy, the user can draw with the pen, create shapes, rotate shapes, scale shapes, move shapes, change the color of shapes, and more. Consider these as actions that the user wants to perform.
The commands you write should be able to be undone and then redone. Think about what a command needs to know about in order to undo its action. When you want to undo an action, you want to return the program to the state it was in before the action was performed. So, your commands are probably going to need to store some information about the state of the program before and after the action occurred. The information that needs to be stored depends on what kind of command it is.
For example, if you were modeling a command that changed a shape’s color, you’d probably want to store the shape’s color before and after the command was performed. You could then use this information to undo and redo the command.
Let’s take a look at how undo and redo work for the user. Undo should “reverse” the last action that was performed. This last in, first out pattern should remind you of a Stack
. Each command that a user performs should be pushed onto a Stack
of previous commands. Each time a user tries to undo an action, simply pop off the last command that was performed and undo it.
But how do we undo and redo different kinds of actions? This is where you will use your command pattern. Although undoing a change in color is different from undoing a change in size, this difference is captured by the undo()
and redo()
methods of each implementing / inheriting class of your Command
interface / superclass. Because of polymorphism, we can have a Stack
of Command
objects and when you call undo()
or redo()
on one of its elements, the correct implementation of the undo()
or redo()
will be invoked.
Since each of the commands know how to undo and redo, you don’t have to worry about which command you happen to be dealing with. You do know, however, that you are performing them in the right order because you get them off the top of the Stack.
What about redoing? First note that redoing is only available after undoing. Redoing is doing again those things that you’ve undone, in the proper order. Again, a Stack will save us! After undoing a command, you should keep track of it in a similar way that you’ve kept track of all the normal commands: push it onto a Stack. This time, however, use a different Stack.
Take a look at the following diagrams to see how to put this all together:
Performing a new action
Undoing an action
Redoing an action
Sketchy drawings are collections of shapes and lines, each with appropriate settings and values. Saving this drawing is simply the act of keeping a record of each line and its color, and each shape and its attributes. Loading a drawing consists of doing the opposite: reading in which shapes and lines to draw, and setting their attributes.
You’ll need your format to include some mention of whether the object you’re referring to is a shape or just a line. It turns out that for covering base specifications, each shape you use should only have a very limited set of attributes. Each shape will probably have the following:
Parameter | Values | Data Types |
---|---|---|
Type of object | Shape name | String |
Location | x-coord, y-coord | double, double |
Dimensions | degrees | double |
Color | red, green, blue | int, int, int |
Each line will probably have the following:
Parameter | Values | Data Types |
---|---|---|
Type of object | line | String |
Color | red, green, blue | int, int, int |
Array of points | x-coord, y-coord, … | double... |
This information is all your program needs to reconstruct the exact same shape or line when it loads the file later. (You can also store additional information that might help you load shapes and lines!)
If you know that each shape or line is described by the above parameters, then you should be able to go through a list of these, making a new shape or line for every set of data you read.
A short file with three objects may look like this:
Assuming the pattern of variables described above, the first line of this file describes a rectangle at position (15, 10), with a width of 70 and height of 40. It’s rotated 0 degrees and is colored white. The second line of the file describes an ellipse at position (50, 50), with a width of 20 and a height of 20 (it looks like a circle), rotated by 0 degrees and colored red. The third line of the file describes a red line with 6 doubles forming points at (50, 55), (60, 60), (65, 70).
Remember that storing each line will require storing each of its individual coordinates (of type double
). Don’t worry about the length of these entries in your saved file – it takes a lot of space to store all of those points, so it makes sense.
Remember that the above specifications are only suggestions. You have the final say on what your file format will be. You may find that rearranging the parameters would suit you better – that’s fine! If you are considering a file format or parsing technique that is far different from our suggestion, you should consider talking to a TA about it before implementation.
For full functionality, It’s important that saving/loading a drawing preserves the layering of shapes and lines on the canvas. The order in which Nodes
are added to the Scene Graph (the list of children) determines the layering of shapes and lines on the screen. So to save your entire collection of Shapes
and Polylines
, you might consider just iterating through the Pane
’s list of children. However, this will not work. When we add Rectangles
, Ellipses
, Polylines
, etc. to the children list, we know their specific type. However, the children list stores objects of type Node
, so if we iterate through that list, we will only be able to refer to the objects as Nodes
, not as their specific types. Nodes
do not have the properties listed above, such as color, width, height, and rotation.
Since we can’t use the list of children, what data structure can we use to store everything (shapes and lines) that we need to save, while preserving the same order as the Pane’s
list of children? Every object in this data structure must be of the same type, but what type should that be? CurvedLines
and SketchyShapes
don’t inherit from the same supertype, so we’ll need some way of relating these two objects for the purposes of saving…
What do CurvedLines
and SketchyShapes
both need to know how to do? They both need to know how to save themselves! So there’s one possible way to relate these two different types by having them implement/inherit from the same supertype, but should this supertype be an abstract class or an interface? What method(s) would this supertype need to make it possible to save every implementing class?
sketchySupport.CS15FileIO
Great, now I know what to do to save my shapes and lines, but how the @#$% am I going to do all that? FileIO
!!
The cs15.fnl.sketchySupport.CS15FileIO
class is a support class that will do the nitty gritty parts of writing to and reading from files. You don’t need to subclass it. You’ll just create an instance of it and call its methods. To find a list of CS15FileIO
’s methods, consult the Sketchy FidxleIO Javadocs. Make sure to read these carefully before moving on.
So here is how you would use the CS15FileIO
class to make a file that contains the phrase “a b c 1 2 3”
:
Notice the last step. It’s very important. If you don’t call closeWrite()
when you’re done, nothing will print out to the file. Also notice that the order of these method calls is what determines the order of the information in the file. This is sequential file I/O. Calling one of the write methods just adds that information to the end of what has been written to that file since it was opened. A very important thing to realize, though, is that when you call openWrite(filename)
, the contents of the file represented by the String filename
will be deleted and replaced with whatever you write in it. Opening a file for writing and then writing to it DOES NOT just put what you write at the end of that file. Look at the method public static String getFileName(boolean save, Window stage)
to help you out with the initial steps of saving and opening files.
Ok, so now you can write information to a file. But you’re also going to need to read information from a file to be able to load in Sketchy. So let’s read the contents of the file that you just wrote to and store the information in six variables. We assume for right now that you know that you are going to be reading three Strings
followed by three ints
.
String
variables: string1
, string2
, string3
and three int
variables: int1
, int2
, int3
.openRead(filename)
where filename
is a String
representing the name of the file that we wrote to before.string1 = fileIO.readString();
string2 = fileIO.readString();
string3 = fileIO.readString();
where fileIO
is a reference to the sketchySupport.CS15FileIO
instance that you created.ints
: int1 = fileIO.readInt();
int2 = fileIO.readInt();
int3 = fileIO.readInt();
closeRead();
Now, let’s talk about how to apply these methods to saving and loading in Sketchy.
Saving will involve iterating over all of the shapes and lines that you’ve stored and telling each to save itself. One way of doing this is to give the objects that represent your shapes and lines a method that takes in a CS15FileIO
object with which it saves itself. What your shapes and lines should be writing to the file are the values of their attributes described above.
Remember that how you write to a file will depend on how you expect that file to be read. Therefore, make sure you do some pseudocoding for both the saving and loading processes before writing them.
To load a file, you will basically open a file and work through it, shape by shape, until you reach the end. (Before doing this, you’ll want to logically and graphically remove everything currently on the canvas). Start out by using the CS15FileIO
class to open a file for reading. While (wink, wink) there is more to read in the file (think fileIO.hasMoreData()
), you should assume that there is another shape to parse out. Again, using your “smart shapes”, parse out the expected data types and use them to construct a new shape.
Based on the format you used to save your shapes and lines, you will know in what order their attributes are stored in the file. If you know the next piece of data will be an int
, use the readInt()
method to get the next integer. If it will be a double
, use the readDouble()
method to get the next double
. If it’ll be a String
, use the readString()
method to get the next String
. You’ll probably want to use the name of the shape to determine which type of shape you’re going to construct. This gives you a way to know what the next data type must be, because you yourself designed the file format!
MouseEvents
are objects that contain information about a mouse input event, and they are extremely important for this project.
You will want to define EventHandlers
that handle user mouse input. It is important to note that there are different types of MouseEvents
. See the Field Summary in the MouseEvent
Javadocs for a list of them (you will not have to use all of the listed EventTypes
). Here’s a little bit of information about a few you might be interested in:
MOUSE_PRESSED
is triggered when the mouse is pressed. It only refers to the initial press - pressing and holding will not produce additional MOUSE_PRESSED
events.MOUSE_DRAGGED
is triggered when the mouse has been pressed and draggedMOUSE_RELEASED
is triggered only when the mouse is releasedA way to do this is to define a different EventHandler
for each type of MouseEvent
you will need to respond to. Adding these EventHandlers
is similar to how we’ve handled KeyEvents in the past - the same way we used pane.setOnKeyPressed((KeyEvent e) -> methodCall())
, there are relevant methods for adding EventHandlers
for MouseEvents
(hint: setOnMousePressed
, setOnMouseDragged
, setOnMouseReleased
). Check out the Node
Javadocs for the full list of methods!
You can find out information about what the user did with the mouse by calling methods on a MouseEvent
. For example, you will need to get the location of MouseEvents
in order to resize and rotate the selected shape. To get the location of a MouseEvent
, just call getX()
and getY()
.
Finally, you can use some of the methods of the MouseEvent
class to detect which mouse button was pressed, or what keys were held down in conjunction with a click. They include:
public final boolean isPrimaryButtonDown();
(AKA left mouse button)
public final boolean isControlDown();
public final boolean isShiftDown();
So, for example, if you want to check if the left mouse button is the one that was used, you can just say:
if (e.isPrimaryButtonDown())
where e
is a MouseEvent
. Check out the MouseEvent
Javadocs for more information and more methods you can use.
You might have noticed that we’ve given you a few extra files: the main
, commands
, and shapes
packages. A package is a way to organize classes when you have a large program. In fact, you’ve been using packages this whole time! When you cloned Tetris from GitHub and renamed it, that became the tetris
package! Similarly, we now have a sketchy
package with other packages within it.
The main
package can be thought of as the control center for your program, containing the App.java
class as well as other general-purpose classes. The commands
package will contain the classes that represent commands, such as creating the shape or resizing. The shapes
package will contain the classes modeling the shapes and lines on the Sketchy canvas.
Note that because of our multi-package structure, typing javac *.java
will not compile your code properly—it will be best to make use of the green play button for this project. Having these packages will make it easier for you as you’re incrementally coding to keep track of all your different classes. Also, since these packages are mainly for organization, you are free to alter them in whatever way makes most sense to you, as long as it’s still a coherent design. Just make sure to explain your choices in the README!
In previous projects, all of your classes have been inside a single package. Now with multiple packages, the first line of each file will say
You can see this in the top line of the App class we gave you, which reads
Since App
is in the main
package. When it comes to instantiating classes in a class in one package that are within a different package, we need to use import statements! Say we’re coding a class in the main
package and we want to instantiate a class that is in the shapes
package. Above the class header comment, you would write
You can also hover over the red error that shows up when you try to instantiate without importing and click the “import class” button.
An enumerated type (or enum
), like a class or interface, is a specific programming pattern that can abstract groups of constants. (in your case, such as the selected editing mode on the radio buttons!) Enums bear similarities to both objects and primitive types. An enum is defined with a set of constants, conventionally named in all caps. Below is an example of how to declare an Operation
enum type:
A unique property of enums is that they can be used in a switch statement like ints
(though they are not ints
). For example:
Switch statements are particularly useful with enums
. The method above uses an example of a switch statement that switches based on the value of the particular instance of the enum
of type Operation
. Depending on the type of Operation (either PLUS
, MINUS
, TIMES
, or DIVIDES
), the method performs a different mathematical operation on its parameters. Switch may be useful in some of your methods, including your MouseEvent
handlers and in any code that changes its execution based on which RadioButton
is selected.
You cannot instantiate enums with new
. To get a specific value, use something like Operation.PLUS
or Operation.MINUS
as you would with a Constants class. For example, if you have a method that takes in an Operation
as a parameter, then Operation.PLUS
would be an acceptable value to pass in.
Refer to the CS15 Style Guide for the specific style guidelines along which your code will be graded.
In CS15, you’re required to hand in a README file (must be named README) that documents any notable design choices or known bugs in your program. Remember that clear, detailed, and concise READMEs make your TAs happier when it counts (right before grading your project).
You are expected to create your own README file. Please refer to the README guide for information on how to create a README, what information your README should contain, and how you must format it. In addition to describing your design choices, if you decide to implement any extra credit, please detail it in an EXTRA CREDIT section of your README.
At the bottom of your README, add the approximate number of hours you spent on this assignment. This will be used only to average how long the assignments are taking students this semester, and it is completely anonymous.
To hand in your assignment, follow these steps:
sketchy
folder.rm *.class
(Mac) or del *.class
(Windows).java
code files.If you do not submit all the proper files to Gradescope, we will allow you to resubmit with a 5 point penalty, only if you’ve pushed your code to GitHub prior to the deadline. If your code wasn’t pushed to GitHub by the deadline and you submit incorrectly to Gradescope, we will grade whatever was submitted.
You can submit as many times as you want prior to the deadline, and only your most recent handin will be graded. If you handin before the deadline and again after the deadline, the submission will be counted as late.
Do not include any identifying information on your handin (name, login, Banner ID) as we grade anonymously. Including identifying information will result in a deduction from your assignment.