FinalProject Checkpoint6
FinalProject Checkpoint6
- Checkpoint 6
Checkpoint Description
Alright! Time for our FINAL checkpoint! With the vast majority of our planned features now implemented, we’re going to sweep through our
program and bring in a bunch of extra functionality.
Objectives
1. Objective 1
2. Objective 2
3. Objective 3
Objective 1
Objective checklist:
• Make our program save images to somewhere besides the desktop
• Lock the size of our window
Coming up with a REALLY good solution here might take a little bit of time and effort, so let’s just do things the easy way instead and make our
program save files out to the same folder we opened them up from.
Add the following method to ImageInputOutput.java
private String scrapeFolderLocation(String filePath) {
return null;
}
This method will take the full file path of any image we load up
C:Users\\SomeUser\\Desktop\\SomeFile.bmp
And chop off the file name at the end, just giving us the folder location
C:Users\\SomeUser\\Desktop\\SomeFile.bmp
Doing this is a simple matter of simple String traversal
private String scrapeFolderLocation(String filePath) {
StringBuilder builder = new StringBuilder(filePath);
for(int i = filePath.length() - 1; i >= 0; i--) {
if(filePath.charAt(i) != '\\') continue;
return builder.substring(0,i);
}
return null;
}
Method loops until it hits that first \\, then grabs everything that came before it
Saving and loading to the Documents folder for example, should now be possible
And done!
Clicking down on a button, moving the mouse off the button, then letting go of the click
If performing the steps above produces the result you see above, then this is caused because we destroyed a small piece of functionality of our
program when we switched towards our opt-in collision detection system. You can fix this by making the following change inside of mouseMoved in
InputManager.java
public boolean mouseMoved(int screenX, int screenY) {
IHoverable collision = CollisionManager.Instance.getHovered(
new Vector2(screenX, ImageEditor.Instance.ScreenSize.y - screenY)
);
if(_currentlyHovered != null && _currentlyHovered != collision) _currentlyHovered.onHoverExit();
if(collision != null) collision.onHovered();
if(collision != _currentlyHovered) _currentlyClicked = null;
This sets _currentlyClicked to null whenever we unhover something, taking care of this edge -case
Right now, our entry point is a bit of a mess. We’ll be adding to it in the next Objective as well, so let’s take a little bit of time and clean it up.
Your code might look just a little different, but this is what my create() method looks like right now
public void create () {
Instance = this;
new ImageInputOutput();
InputManager inputManager = new InputManager();
Gdx.input.setInputProcessor(inputManager);
batch = new SpriteBatch();
ScreenSize = new Vector2(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
Vector2 editWindowSize = new Vector2(500, ScreenSize.y - 50);
_editWindow = new EditWindow(editWindowSize, new Vector2(ScreenSize.x - editWindowSize.x, 0));
Button button = new Button(new Vector2(50, 50), Vector2.Zero, Color.GOLD);
CollisionManager collisionManager = new CollisionManager();
}
No spacing, organization, or labelling
I won’t go into excess detail here, but this is how I cleaned up my code
public void create () {
Instance = this;
initializeUtilityClasses();
createGraphicalElements();
}
private void initializeUtilityClasses() {
new CollisionManager();
new ImageInputOutput();
InputManager inputManager = new InputManager();
Gdx.input.setInputProcessor(inputManager);
}
private void createGraphicalElements() {
ScreenSize = new Vector2(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
Vector2 editWindowSize = new Vector2(500, ScreenSize.y - 50);
Button button = new Button(new Vector2(50, 50), Vector2.Zero, Color.GOLD);
batch = new SpriteBatch();
_editWindow = new EditWindow(editWindowSize, new Vector2(ScreenSize.x - editWindowSize.x, 0));
}
Objective 2
Checklist:
• Let’s actually DO SOMETHING with our Button class
o Create ColorButton.java
o Create a bunch of color buttons along the left-side of the screen
o Add Outlines to Buttons
• Let’s also create some Menu Buttons
o Create Save Button
o Create Exit Button
o Create Black & White Button
2.0 Color Buttons
Right now, there are two parts of our program that I find a little disappointing still.
ONE: We have this awesome Button class that we built forever ago, and it’s not really used for anything
TWO: We can only ever doodle in a single color
Now, a Color Button will act VERY similarly to a Button, except, it’ll have some extra functionality when you click on it. It’ll set the color that we’re
doodling with to the color of the Button itself.
We’ll start by just overriding onClickUp() inside ColorButton
public void onClickUp(Vector2 clickPosition) {
super.onClickUp(clickPosition);
System.out.println("I've been clicked");
}
Doing this will allow our Color Button to be clicked like normal, but with extra features added on top!
We can test this out, by switching our Button in ImageEditor out for a ColorButton instead.
Button originalButton = new Button(new Vector2(50, 50), Vector2.Zero, Color.GOLD);
ColorButton button = new ColorButton(new Vector2(50, 50), Vector2.Zero, Color.GOLD);
Neat!
We can now add a new method to our EditWindow called setDrawColor(), and call it from ColorButton.onClickDown()
public void setDrawColor(Color newColor) {
DoodleMap.setColor(newColor);
}
Setting the color of our DoodleMap to a new color
We’ll be accessing it in just a moment, so hop into Button and set _startColor to protected
public class Button extends Rec2D implements IClickable, IHoverable {
protected Color _startColor;
private Color _hoveredColor;
And now, just call the setDrawColor method from inside onClickDown
public void onClickDown(Vector2 clickPosition) {
super.onClickDown(clickPosition);
EditWindow.Instance.setDrawColor(_startColor);
System.out.println("I've been clicked");
}
Using that new method to set the color of our DoodleMap to the color of the Color Button
In Outline, we can fill the top row of pixels with our selected Color by doing the following-
//Top
for(int x = 0; x < map.getWidth(); x++) {
for(int y = 0; y < thickness; y++) {
map.drawPixel(x, y);
}
}
Draw thickness many horizontal lines at the top of our Pixmap
We can verify that this is working by adding an extra loop to draw all the Outlines in our Rectangles inside our render() method
batch.draw(_editWindow.DoodleTexture, _editWindow.Position.x,
_editWindow.Position.y, _editWindow.Scale.x, _editWindow.Scale.y);
for(int i = 0; i < Rectangles.size; i++) {
rec = Rectangles.get(i);
batch.draw(rec.Outline.OutlineTex, rec.Position.x, rec.Position.y, rec.Scale.x, rec.Scale.y);
}
Running your code should now show black outlines along the top of all our Color Buttons
The outlines on the black and white buttons blend in a little, but that’s fine
2.2 Save Button
Right now, saving our program can be done by pressing CTRL + S. This is awesome for what I’ll call “power users” who know how to use program
shortcuts, but consider that not everyone might know this shortcut even exists, let alone feel comfortable using it regularly.
So let’s add some Buttons along the top of our program that let us expose existing functionality to our users.
Create a new class called SaveButton.java that extends Button and overrides onClickUp()
public class SaveButton extends Button {
public SaveButton(Vector2 scale, Vector2 position, Color recColor) {
super(scale, position, recColor);
}
public void onClickUp(Vector2 clickPosition) {
super.onClickUp(clickPosition );
System.out.println("Clicked on the save button");
}
}
We can even add the ability for our Buttons to have some Text to make it more obvious what their functions are supposed to be!
public class Button extends Rec2D implements IClickable, IHoverable {
public String ButtonText;
We can then configure our program to render text out to the screen as desired
Hop into ImageEditor and add a BitmapFont to the class
private BitmapFont _font;
private void initializeUtilityClasses() {
new CollisionManager();
new ImageInputOutput();
InputManager inputManager = new InputManager();
Gdx.input.setInputProcessor(inputManager);
_font = new BitmapFont();
_font.getData().setScale(2f);
}
Creating the font and scaling up a little so our text isn’t super small
We can now hop over to render() and add some code to render out any text (AFTER rendering everything else)
for(int i = 0; i < Rectangles.size; i++) {
rec = Rectangles.get(i);
if(rec instanceof Button) {
button = (Button) rec;
if(button.ButtonText == null) continue;
_font.draw(batch, button.ButtonText, button.Position.x, button.Position.y + button.Scale.y * 0.75f,
button.Scale.x, Align.center, false);
}
instanceof checks to see if a rectangle is also a Button. We can then safely cast our rectangle INTO a Button so we can access ButtonText.
Running our code now, we should see some descriptive text above our Button!
Hurray!
Whoah whoah whoah, slow down! What even IS all this new stuff you’re throwing in?
Well…I promised this would be a victory lap, so I’ll spare you a lengthy explanation just this once. If you find it a satisfy ing answer, it’s all
just MAGIC!
If you want a real answer though, I’ll just refer you to this article on text rendering in LibGDX
https://libgdxinfo.wordpress.com/basic-label/
Our text is a bit blurry though. Unfortunately, this can be a bit of a pain to fix properly, so we’ll just pretend to fix it instead, by making the text (and
the Button it’s inside of) SMALLER!
Start by removing our call to setScale()
private void initializeUtilityClasses() {
_font = new BitmapFont();
_font.getData().setScale(2f);
}
So long!
Finally, the color of our Save Button, and our Edit Window (when it doesn’t have any image imported yet) are a little too similar. Let’s hop into
EditWindow, and make it use another color beside Gray.
public EditWindow(Vector2 scale, Vector2 position) {
super(scale, position, Color.SLATE);
You might find this color a little ugly. Feel free to experiment
And now we should have some text that is smaller, but less blurry!
A tradeoff, certainly, but again, easier than actually solving the problem
Lastly, let’s make this Button perform its intended function. This is perhaps the most surprising part, as, due to the structure of our program, this is
INCREDIBLY easy to do! Add the following to onClickUp inside SaveButton
public void onClickUp(Vector2 clickPosition) {
super.onClickUp(clickPosition);
if(ImageInputOutput.Instance.ImageFolderLocation == null) return;
try {ImageInputOutput.Instance.saveImage(ImageInputOutput.Instance.ImageFolderLocation + "\\output.bmp");}
catch (IOException e) {e.printStackTrace();}
}
No way it’s actually that easy, right?
Turns out it IS that easy!
Now go ahead and create an ExitButton, and place it next to your Save Button
It should now render inside our program, and, when clicked on, close the application
Showing this off is a little difficult on my end, but I trust you can figure out how to test this one
We’ll actually start by making some quick changes to EditWindow. We’ll add a proper variable to track the current drawing color, and get rid of our
setDrawColor method.
public class EditWindow extends Rec2D implements IClickable{
public Color DrawColor;
public void setDrawColor(Color newColor) {
DoodleMap.setColor(newColor);
}
This’ll pay off in just a moment
Didn’t you just have us create this method? What’s the deal?
Yes, I did! I did this because I wanted to show off the design process for building out new features. When writing code, we want t o initially
try to lock down every piece of data we can find. If a variable MUST exist, then it should default to private , and If a method MUST be run,
we should limit its use and functionality as much as possible. This stops future programmers from doing dumb things with our code. At
first, our code was fine with a locked -down setColor method, but we’ll soon be wanting to both SET and GET this data.
Now, as before, let’s add a new Button to our program, and call it ClearDoodleButton
public class ClearDoodleButton extends Button{
public ClearDoodleButton(Vector2 scale, Vector2 position, Color recColor) {
super(scale, position, recColor);
ButtonText = "Clear";
}
public void onClickUp(Vector2 clickPosition) {
super.onClickUp(clickPosition);
EditWindow edit = EditWindow.Instance;
edit.DoodleMap = new Pixmap((int) edit.Scale.x, (int) edit.Scale.y, Format.RGBA8888);
edit.DoodleMap.setColor(edit.DrawColor);
edit.DoodleTexture = new Texture(edit.DoodleMap);
}
}
Recreating our Pixmap and setting its Color to the Color the previous one had
Go ahead now and add your Clear Button to your program, and you should be able to test out its functionality!
Objective 3
Checklist:
• Clean up our program structurally
• Make Saving a multi-threaded activity
3.0 Creating Packages
By this point, if we take a look at our code files in our Package Explorer, you’ll notice that things have become a bit cluttered and difficult to navigate
How quickly can you find Outline.Java? Do your eyes ever skim past a file on accident while reading?
So far, organization hasn’t been a problem, but the larger our program becomes, the more difficult it will become to parse through any particular
piece of it. To solve this, we can break out our codebase into a number of packages
In Java, packages act like folders for our code. They let us organize into logical categories, and also let us have two or more class files with the SAME
name in a single project. We can create a new package in Eclipse like this
Right-click -> New -> Package
We’ve been creating a LOT of Buttons recently, so let’s create a new package called buttons
You can now drag-and-drop all of our Button classes into this package.
Selecting Update references will make any classes that have references to our Buttons insert an import statement at the top
Let’s also create a Utility package, and move all of our Managers and such into that package
Much neater!
You can find a checklist in the final-submission assignment to verify that your program is working as intended