FinalProject_Checkpoint3
FinalProject_Checkpoint3
- Checkpoint 3
Checkpoint Description
So far, we’ve created the basic structure necessary for simple interactivity. In this checkpoint, we are going to begin working on the image
editing functionality of our application. This entire checkpoint focuses almost entirely on a single feature-set.
The edit window will be where we will be able to see and edit images in our application. It is going to be the most fundamental piece of
functionality for our program, so we’re going to focus entirely on getting a basic version of it up and running.
The Edit Window is where we’ll be doing all of our image processing and doodling
Objectives
1. Objective 1
2. Objective 2
3. Objective 3
Objective 1
Objective checklist:
• Clean up our code from last time
o Make rendering scale better by shoving everything into an Array
I’ve introduced the idea before that it is bad practice to have copy-paste lines like this, but what’s honestly so bad about the code above?
Perhaps consider, that if I run this code, that this is what my program looks like.
This is happening because, in all my copy-pasting madness, I forgot to properly change some of my variables. Can you spot where I messed up
above? If not, or if you want to see if you’re right, I’ve highlighted the mistakes below.
Then, inside render(), you can create a loop that renders out all of our rectangles for us.
Rec2D rec;
for(int i = 0; i < Rectangles.size; i++) {
rec = Rectangles.get(i);
batch.draw(rec.RecTexture, rec.Position.x, rec.Position.y, rec.Scale.x, rec.Scale.y);
}
Checklist:
• Create more interactive elements for our Button class
o Hovering over
o Unhovering
o Clicking up
• Make InputManager activate the interactive code we’ve put inside Button
2.0 OnHover
Before moving on to the edit window, let’s take a look at some more of the code from the previous checkpoint, and expand the capabilities of our
code a little. As-is, it’s a little difficult to tell when a button has been pressed.
Sure, we’re printing out to the console window, but the console isn’t coming along with us when we package things up as a .jar file for other people
to run.
We’ll do this by creating behavior that can change the _recColor of a Button whenever it has been clicked on or hovered. We can start by adding a
method to our Button class, called onHovered().
Now, so far, this is cool and all, but we just mentioned that the console window isn’t a good way of showing information to our users, so instead of
printing a message, let’s change the color of our Button when it’s been hovered over!
To do this, we’re going to need to edit _recColor. Button inherits from Rec2D, but doesn’t have access to _recColor as it’s private. A getter/setter
could be used, but I’ll just try to keep some amount of control over access to this variable by setting it to protected. (As I’m lazy)
Now, when onHovered() is called, we can modify the color of our Button to be twice as dark. This is quite trivial to do, as the rgb values of a Color all
get darker as they approach 0.
…nothing
This is because of our approach to building our Textures. When we create our Button, we call generateTexture() up in Rec2D. Recall that this is the
method where we draw in the color-data of our Texture. Changing the variable we used for color unfortunately doesn’t change that underlying
color-data that we painted in.
HOWEVER
We can just call generateTexture() again after changing the color, and it will set all of your pixel-data to this new Color!
_recColor = new Color(_recColor.r / 2f, _recColor.g / 2f , _recColor.b / 2f, 1);
generateTexture();
NOTE: This operation is VERY expensive to run, as we regenerate our Texture every single frame that our mouse is hovering
over our Button. It is NOT recommended that you use this exact approach in-industry for this reason. We’re choosing it only
for the sake of simplicity.
Our problem is that we only want to set our Rec2Ds Color to half of its ORIGINAL value, but we keep overriding our data, making it darker and
darker. We can fix this by using a variable that keeps track of the original color of our Rec2D
private Color _startColor;
public Button(Vector2 scale, Vector2 position, Color recColor) {
super(scale, position, recColor);
_startColor = recColor;
Now, as long as we NEVER change the value of _startColor, we can safely use it in our onHovered() method to change _recColor.
_recColor = new Color(_startColor.r / 2f, _startColor.g / 2f , _startColor.b / 2f, 1);
Replacing _recColor / 2f with _startColor
Now open up your program again, and try hovering over your Buttons, and they should change color properly!
2.1 OnHoverExit
Right now, our system is only capable of detecting when a Button has been hovered over, but it has no concept of when we have STOPPED hovering
over an item with our cursor.
If we only knew when a Button is no longer being hovered over, we could tell it to return back to its _startColor.
So let’s add a new method to Button, called onHoverExit() to do exactly this, and have it set the current _recColor back to _startColor.
public void onHoverExit() {
_recColor = _startColor;
generateTexture();
}
When this method is called, we set our Button back to its original color.
Now all we need to do is figure out HOW to determine that our mouse has EXITED the bounds of a Button
The following are rules that we can use to tell when a Button has become “unhovered”
Mouse hovering nothing, then hovering the Blue Button once moved, then after moving off the Blue Button, the Blue Button becomes
unhovered
You will know that you have succeeded when you can recreate the behavior in the screenshots from above
Hurray! We can now tell when a Button has been hovered over with the mouse
A very simple way of doing this is to simply make the Button DARKER when we click on it. Darker even, than when we’ve hovered over it.
Perform the following:
• Hop into onPressed(), in InputManager.java
• Set the Color of your Button to 1/4 of its original brightness.
If you do it right, you should see that your Button goes even Darker once clicked!
The Blue Button goes from being hovered, to clicked, becoming even darker
However, if we keep our mouse perfectly still after letting go, our Button remains the same “clicked” color
This is happening because our code lacks the ability to understand when a Button has become “unclicked”. It only changes color when we move the
mouse because onHovered() is getting called. We can fix this by adding a new method to handle when a Button has become “unclicked”
The following should give you the code we’ll need to change our color again when we “unclick” a Button. However, we still have to call onClickUp()
to make our code do anything new.
If all went successful, you should now be able to click away to your heart’s content on your Buttons!
Unless….
We move our mouse around while holding down a click. Then our Button doesn’t update anymore.
If we move our mouse off the Button that we clicked on, it stays dark.
This happens because LibGDX, being a multiplatform library, splits up moving the mouse, and dragging a clicked mouse (largely because touch-
dragging is a big deal in mobile) To get started fixing things –
Perform the following:
• Go to InputManager.java
• Find touchDragged()
• Make it call mouseMoved()
o Pass screenX and screenY into mouseMoved()
This is because now we’re overriding the fact that our Button is clicked whenever our mouse is moved. The way to fix this is to add the concept of
STATE to our Buttons. If a Button can REMEMBER that it is currently being clicked, it can stop itself from overriding the color of the Button when
onHovered() gets called.
The easiest way to do this, is to tell our code that a Button can be either clicked on or hovered over
But NEVER both at the same time.
There are a number of ways we can do that, but we’ll be taking the approach of using enums, as they’re easily scalable, and very human readable.
And hurray!
2.3 Testing
To verify that your code is working, you should now be able to get the following results in your program:
Objective 3
Checklist:
• Build an Edit window to draw doodles on
o Create EditWindow.java
o Give it a Texture to draw doodles to
• Rewire our input management
o Make our program able to have any class pick and chose “how” it can be interacted with
• Fix up our ability to draw a little by letting us “drag” across our edit window
3.0 EditWindow.java
Alright! Finally! It’s time to work on some actual functionality that ISN’T just general setup. Let’s start by removing the Buttons that we have already
created, as they won’t be any particular use to use going forwards.
Button button1;
Button button2;
Button button3;
Button button4;
Button button5;
SpriteBatch batch;
public void create () {
Instance = this;
InputManager inputManager = new InputManager();
Gdx.input.setInputProcessor(inputManager);
batch = new SpriteBatch();
ScreenSize = new Vector2(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
Vector2 rectangleScale = new Vector2(100,100);
button1 = new Button(
rectangleScale,
new Vector2(ScreenSize.x / 2f - rectangleScale.x * 2, ScreenSize.y / 2f - rectangleScale.y * 1.5f),
Color.ORANGE);
button2 = new Button(
rectangleScale,
new Vector2(ScreenSize.x / 2f + rectangleScale.x, ScreenSize.y / 2f - rectangleScale.y * 1.5f),
Color.GREEN);
button3 = new Button(
rectangleScale,
new Vector2(ScreenSize.x / 2f + rectangleScale.x, ScreenSize.y / 2f + rectangleScale.y / 2f),
Color.BLUE);
button4 = new Button(
rectangleScale,
new Vector2(ScreenSize.x / 2f - rectangleScale.x * 2, ScreenSize.y / 2f + rectangleScale.y / 2f),
Color.RED);
button5 = new Button(
rectangleScale,
new Vector2(ScreenSize.x / 2f - rectangleScale.x / 2f, ScreenSize.y / 2f - rectangleScale.y / 2f),
Color.WHITE);
Removing all references to our Buttons , giving us an EMPTY program
Let’s also get rid of the print statement inside of our Button class
public class Button extends Rec2D implements IClickable, IHoverable {
private static int _buttonCount;
private int _buttonNumber;
…
public Button(Vector2 scale, Vector2 position, Color recColor) {
_buttonCount +=1;
_buttonNumber = _buttonCount;
…
}
public void onClickDown(Vector2 mousePosition) {
…
System.out.println("You've pressed button " + _buttonNumber);
}
All references to _buttonCount and _buttonNumber removed from the program.
The final big goal for this checkpoint is going to be creating a basic editing window that we can draw crude little doodles on. Since this is going to
eventually require a lot of unique functionality, we are going to create a brand-new class, EditWindow.java.
Our edit window is going to be an interactive element, so for now, we are also going to have it extend Button, as that’s the only class we have set up
that can handle user input.
public class EditWindow extends Button{
public EditWindow(Vector2 scale, Vector2 position, Color backgroundColor) {
super(scale, position, backgroundColor);
}
}
Calling the constructor for Button inside our new EditWindow class
And we should see that our window looks something like this
This shape was chosen to leave some space for future items we’ll be adding
Now, our Edit Window is going to be eventually doing a WHOLE LOT more than just a basic Button, so let’s override some of the methods of Button,
so we can eventually add our own functionality.
public EditWindow(Vector2 scale, Vector2 position, Color backgroundColor) {
super(scale, position, backgroundColor);
}
public void onClickDown(Vector2 clickPosition) {
super.onClickDown(clickPosition);
System.out.println("Clicked on the Edit Window");
}
super is the keyword that calls anything in the parent class
If you click on your EditWindow, you should now see a message in your console, confirming that our EditWindow code is running.
Woo!
Didn’t we just spend a bunch of effort to remove any code dealing with the console window?
And that’s why we’re going to immediately remove it! I just wanted to make sure that everything is functional before going forwards.
Now, for today, we’re not going to go very far with the Edit Window, but let’s get some basic functionality working. Let’s make it so that we can draw
some doodles on top of our window.
How do we do that?
Would it surprise you to know that we can do it without any new tech or structure than what we’ve already covered?
To draw on top of our Edit Window, we’ll use the same tech we’ve been using so far to draw everything else, a Pixmap and a Texture.
Add the following to your EditWindow
public class EditWindow extends Button{
public Texture DoodleTexture;
private Pixmap _doodleMap;
public EditWindow(Vector2 scale, Vector2 position, Color backgroundColor) {
super(scale, position, backgroundColor);
_doodleMap = new Pixmap((int) scale.x, (int) scale.y, Format.RGBA8888);
_doodleMap.setColor(Color.ORANGE);
DoodleTexture = new Texture(_doodleMap);
}
Creating a Pixmap of the same dimensions as the Edit Window itself
Now when the user clicks on our EditWindow, let’s draw over the pixel they clicked on.
public void onClickDown(Vector2 clickPosition) {
super.onClickDown(clickPosition);
System.out.println("Clicked on the Edit Window");
_doodleMap.drawPixel((int) (position.x),(int) (position.y));
DoodleTexture = new Texture(_doodleMap);
}
Now let’s also draw our little doodle in our render() method.
for(int i = 0; i < Rectangles.size; i++) {
rec = Rectangles.get(i);
batch.draw(rec.RecTexture, rec.Position.x, rec.Position.y, rec.Scale.x, rec.Scale.y);
}
batch.draw(_editWindow.DoodleTexture, _editWindow.Position.x,
_editWindow.Position.y, _editWindow.Scale.x, _editWindow.Scale.y);
Note how we’re drawing our doodle AFTER we’ve drawn everything else.
WARNING: If you suffer from epilepsy, either click slowly, or perhaps better yet, skip some of the following testing steps as there WILL be flashing imagery
If you try running our program, you can now see that whenever you click, orange little dots will show up on the screen.
Our pixels don’t show up where our mouse is pointing. This is because our position variable tells us where on our screen, our mouse is pointing,
NOT where on our Pixmap we should be drawing in pixels.
ONE: Our Pixmap starts at an X-Position of 84. If I click on the left-most pixel of my edit window clickPosition would have an X-value of 84. Our code
as-is, then moves 84 EXTRA pixels to the right, and places an orange pixel there
TWO: Our world-coordinates have a Y-Value of 0 at the bottom of the screen. A Pixmap has a Y-Value of 0 at the top of the Pixmap
Clicking on (50,200) will place an orange pixel 50 pixels to the right, and 200 pixels down from the TOP-LEFT corner of our Pixmap
After performing the above steps, you should be able to draw dots EXACTLY where your cursor is pointing.
In this case, the features we’ve gained from Button are actually providing BLOAT to our program in terms of unnecessary and unwanted features. To
fix this, we are going to decouple the way we process user input.
Why?
While we can perhaps solve our current issue by other means, decoupling our code in this manner will give our codebase much more flexibility in
the long term as we continue adding features.
If I don’t want my Edit Window to do anything when it’s been hovered over, I’ll just choose not to implement IHoverable.
That’s the idea, but we’ve still got some work to do until this becomes possible!
Let’s also change up our EditWindow. We’ll no longer make it extend Button, and we’ll also have it implement JUST IClickable
public class EditWindow extends Rec2D implements IClickable{
…
public void onClickDown(Vector2 position) {
System.out.println("Clicked on the Edit Window");
super.onClickDown(Position);
_doodleMap.drawPixel((int) (position.x - Position.x),(int) (Scale.y - position.y));
DoodleTexture = new Texture(_doodleMap);
}
@Override
public void onClickUp(Vector2 mousePosition) {
// TODO Auto-generated method stub
}
Note how implementing IClickable has forced us to add an onClickUp method
Why are we now extending Rec2D
We wanted to get rid of some of the special features of buttons, but remember earlier that we set our program up to ONLY be able to draw
rectangles. There are still plenty of features of Rec2D that we want to actually keep.
And hurray! If we now open our program and mouse over our Edit Window
It….does nothing
This is because our InputManager still expects everything interactable in our program to still be of type Button. We however, now have items we
want to interact with that could be of any kind of class at all!
Just as long as those classes implement either IClickable or IHoverable
Hmmm….
So, what if, instead of keeping something like an Array of type Button, we had TWO Arrays, of type…
Well, then we could rewire our InputManager to make use of any data type in the world, just as long as it’s marked as either clickable or hoverable.
If you do this, you might reach a stage where your code looks something like this-
Our collision detection algorithm depends on access ing a variable called Position, which doesn’t exist in either of our interfaces
Let ’s do a bit of a sneaky hack. Bear with me on this one
Recall that I mentioned it would be VERY important that EVERYTHING in our program be made up of Rectangles?
Well, what kind of objects are going to be stored in either of these arrays?
Rectangles!
I mean, yeah, Buttons and our EditWindow
But guess what both of those classes inherit from?
Rec2D
This is INCREDIBLY important, because it lets us get away with things that would normally cause our code to crash
If we know ahead of time that everything in both of these Arrays is actually a Rec2D, we can CAST any item from these arrays to a Rec2D
public IHoverable getHovered(Vector2 coordinates) {
Rec2D hovered;
for(int i = 0; i < InputManager.Instance.Hoverables.size; i++) {
hovered = (Rec2D) InputManager.Instance.Hoverables.get(i);
if(coordinates.x > hovered.Position.x && coordinates.x < hovered.Position.x + hovered.Scale.x) {
if(coordinates.y > hovered.Position.y && coordinates.y < hovered.Position.y + hovered.Scale.y)
return (IHoverable) hovered;
}
}
return null;
}
Is your mind exploding? I hope it ’s exploding right now
After performing all this, you should now be able to paint pixels onto your EditWindow without the entire window wildly changing color.
You should also still be able to hover over and interact with your Button as before. You’ve done everything correctly if you can see the following
results from performing the listed operations.
Hovering, Unhovering, Clicking Down, Clicking Up, Clicking Down then Unhovering then Clicking Up
NEAT!
3.2 Improving Our Drawing Ability
So far, we’ve created a TON of Button functionality, and also created a canvas upon which we can now paint.
Very….very slowly…
Each time we click down, we add only a SINGLE pixel, to our little canvas. Let’s change this and add some functionality to our interactable items for
when we drag a clicked mouse across their surface.
This will cause both EditWindow and Button to error out. Hop in and override onClickDragged() in each class.
public class EditWindow extends Rec2D implements IClickable{
…
@Override
public void onClickDragged(Vector2 clickPosition) {
// TODO Auto-generated method stub
}
You can get rid of the @Override if you want
Now, we have a method that we can call when we have dragged a clicked mouse across the surface of our EditWindow. We can call our new
onClickDragged method as follows
public boolean touchDragged(int screenX, int screenY, int pointer) {
mouseMoved(screenX, screenY);
if(_currentlyClicked != null)
_currentlyClicked.onClickDragged(new Vector2(screenX, ImageEditor.Instance.ScreenSize.y - screenY));
We can verify that this is working by making the version of onClickDragged in EditWindow do a little painting.
private void paintAtPosition(Vector2 worldPosition) {
_doodleMap.drawPixel((int) (worldPosition.x - Position.x),(int) (Scale.y - worldPosition.y));
DoodleTexture = new Texture(_doodleMap);
}
public void onClickDragged(Vector2 clickPosition) {paintAtPosition(mousePosition);}
public void onClickDown(Vector2 clickPosition) {paintAtPosition(position);}
I’ve restructured onClickDragged and onClickDown here to avoid any copy-pasting
3.3 Testing
With this change implemented, when you drag your mouse (slowly) across the EditWindow, you should see a nice solid line show up!
Congratulations are definitely in order here! This checkpoint had a couple of sections where you had to do some extending programming without
any assistance. Maybe it wasn’t a problem at all, and maybe you had to put a lot of work into those sections.
Either way, take your burgeoning problem-solving ability within the confines of this project as the progress that it is!
There are still many ways in which you could consider this program basic, but today, you achieved bringing forth one of the most core pieces of
functionality to this project.
From here, you can look forwards to deepening the abilities of our young application by adding things like the ability to change colors, the
functionality to fix the jaggedness of the lines we draw, and more.