package greenfoot.gui.input.mouse;
import greenfoot.MouseInfo;
import greenfoot.gui.input.mouse.MouseEventData;
import java.awt.event.MouseEvent;
import javafx.scene.input.MouseButton;
import threadchecker.OnThread;
import threadchecker.Tag;
| There are two ways that the mouse can be handled in Greenfoot. One is the
| built-in mouse support like it was in Greenfoot release 1.3.0 and earlier.
| This still works the same way in newer Greenfoot versions as long as the
| simulation is not running. When the simulation is running there is no default
| mouse support for dragging objects already added into the world. When the
| simulation is running the user classes have to poll for mouse information
| through the greenfoot.Greenfoot class.
|
| MouseEvents are collected in frames. A frame is defined as the interval
| between the first poll of anything in the previous act-round to the first
| poll in the current act-round.
|
| When the first poll in an act loop happens, the future mouse data is copied
| into current mouseInfo and the creation of a new future mouse data object is
| started.
|
|
| If several events happen in the same frame the events are prioritized like
| this:
| Priorities with highest priority first:
| <ul>
| <li> dragEnd </li>
| <li> click </li>
| <li> press </li>
| <li> drag </li>
| <li> move </li>
| </ul>
|
| In general only one event can happen in a frame, the only exception is click
| and press which could happen in the same frame if a mouse is clicked in one
| frame.
| If several of the same type of event happens, then the last one is used.
|
| If, for instance, two buttons are pressed at the same time, the behaviour is
| undefined. Maybe we should define it so that button1 always have higher
| priority than button2 and button2 always higher than button3. But not
| necessarily documenting this to the user.
|
| @author Poul Henriksen
|
public class MousePollingManager
{
|
| Methods in this class are called from two threads: the simulation thread and the
| GUI even thread. Some methods are called by one and some by the other; the GUI produces
| the data while the simulation consumes it.
|
| A small number of fields can be accessed be either thread and so access to them must
| be synchronized. Other fields are accessed by only one thread or the other.
|
| The current mouse data This will be the mouse info returned for the rest
| of this act loop.
|
| <p>Accessed only from the simulation thread.
|
private MouseEventData currentData = new MouseEventData();
| The future mouse data is build up from mouse events happening from the
| first time the user requested mouse info in this act loop, until the user
| requests again in the next act-loop, at which point the future will
| become the current.
|
| <p>Access to this field must be synchronized.
|
private MouseEventData futureData = new MouseEventData();
| Used to collect data if we already have a highest priority dragEnded
| collected. We need this in order to collect data for a potential new
| dragEnd since we want to report the latest dragEnd in case there are more
| than one.
|
| <p>Access to this field must be synchronized.
|
private MouseEventData potentialNewDragData = new MouseEventData();
| Locates the actors in the world (read only field, requires no synchronization).
|
private WorldLocator locator;
| Keeps track of where a drag started. This should never be explicitly set
| to null, because it might result in exceptions when doing undefined
| things like dragging with two buttons down at the same time.
|
| <p>Accessed only from the GUI thread.
|
private MouseEventData dragStartData = new MouseEventData();
| Track whether the mouse is currently being dragged.
|
| <p>Accessed only from the GUI thread.
|
private boolean isDragging;
| Whether we have received more mouse data since we last gave data to the simulation.
|
| <p>Access to this field must be synchronized.
|
private boolean gotNewEvent;
private boolean gotNewDragStartEvent;
| Creates a new mouse manager. The mouse manager should be notified
| whenever a new act round starts by calling {}link #newActStarted()}.
|
| @param locator
| Used to locate things (actors and coordinates) within the
| World. May be null, but in that case the locator must be set
| later.
|
@OnThread(Tag.Any)
public MousePollingManager(WorldLocator locator)
{
this.locator = locator;
}
| Set the locator to be used by this mouse polling manager.
|
public void setWorldLocator(WorldLocator locator)
{
this.locator = locator;
}
| This method should be called when a new act-loop is started.
|
@OnThread(Tag.Simulation)
public synchronized void newActStarted()
{
if (gotNewEvent) {
MouseEventData newData = new MouseEventData();
futureData.setActors(locator);
currentData = futureData;
futureData = newData;
potentialNewDragData = new MouseEventData();
if (gotNewDragStartEvent)
{
dragStartData.setActors(locator);
currentData.setDragStartActor(dragStartData);
gotNewDragStartEvent = false;
}
gotNewEvent = false;
}
else {
currentData.init();
}
}
| This method should be called every time we receive a mouse event. It is
| used to keep track of whether any events have been occurring in this
| frame.
|
| <p>This must be called from a synchronized context.
|
private void registerEventRecieved()
{
gotNewEvent = true;
}
| Whether the mouse had been pressed (changed from a non-pressed state to
| being pressed) on the given object. If the parameter is an Actor the
| method will only return true if the mouse has been pressed on the given
| actor - if there are several actors at the same place, only the top most
| actor will count. If the parameter is a World then true will be returned
| only if the mouse was pressed outside the boundaries of all Actors. If
| the parameter is null, then it will return true no matter where the mouse
| was pressed as long as it is inside the world boundaries.
|
| @param obj Typically one of Actor, World or null
| @return True if the mouse has been pressed as explained above
|
@OnThread(Tag.Simulation)
public boolean isMousePressed(Object obj)
{
return currentData.isMousePressedOn(obj);
}
| Whether the mouse had been clicked (pressed and released) on the given
| object. If the parameter is an Actor the method will only return true if
| the mouse has been clicked on the given actor - if there are several
| actors at the same place, only the top most actor will count. If the
| parameter is a World then true will be returned only if the mouse was
| clicked outside the boundaries of all Actors. If the parameter is null,
| then it will return true no matter where the mouse was clicked as long as
| it is inside the world boundaries.
|
| @param obj Typically one of Actor, World or null
| @return True if the mouse has been clicked as explained above
|
@OnThread(Tag.Simulation)
public boolean isMouseClicked(Object obj)
{
return currentData.isMouseClickedOn(obj);
}
| Whether the mouse is being dragged on the given object. The mouse is
| considered to be dragged on an object, only if the drag started on that
| object - even if the mouse has since been moved outside of that object.
|
| If the parameter is an Actor the method will only return true if the drag
| started on the given actor - if there are several actors at the same
| place, only the top most actor will count. If the parameter is a World
| then true will be returned only if the drag was started outside the
| boundaries of all Actors. If the parameter is null, then it will return
| true no matter where the drag was started as long as it is inside the
| world boundaries.
|
| @param obj Typically one of Actor, World or null
| @return True if the mouse has been pressed as explained above
|
@OnThread(Tag.Simulation)
public boolean isMouseDragged(Object obj)
{
return currentData.isMouseDraggedOn(obj);
}
| A mouse drag has ended. This happens when the mouse has been dragged and
| the mouse button released.
|
| If the parameter is an Actor the method will only return true if the drag
| started on the given actor - if there are several actors at the same
| place, only the top most actor will count. If the parameter is a World
| then true will be returned only if the drag was started outside the
| boundaries of all Actors. If the parameter is null, then it will return
| true no matter where the drag was started as long as it is inside the
| world boundaries.
|
| @param obj Typically one of Actor, World or null
| @return True if the mouse has been pressed as explained above
|
@OnThread(Tag.Simulation)
public boolean isMouseDragEnded(Object obj)
{
return currentData.isMouseDragEndedOn(obj);
}
| Whether the mouse had been moved on the given object. The mouse is
| considered to be moved on an object, only if the mouse pointer is above that
| object.
|
| If the parameter is an Actor the method will only return true if the move
| is on the given actor - if there are several actors at the same
| place, only the top most actor will count. If the parameter is a World
| then true will be returned only if the move is outside the
| boundaries of all Actors. If the parameter is null, then it will return
| true no matter where the drag was started as long as it is inside the
| world boundaries.
|
| @param obj Typically one of Actor, World or null
| @return True if the mouse has been moved as explained above
|
@OnThread(Tag.Simulation)
public boolean isMouseMoved(Object obj)
{
return currentData.isMouseMovedOn(obj);
}
| Gets the mouse info with information about the current state of the
| mouse. Within the same act-loop it will always return exactly the same
| MouseInfo object with exactly the same contents.
|
| @return The info about the current state of the mouse; Null if the mouse is outside
| the world boundaries (unless being dragged).
|
@OnThread(Tag.Simulation)
public MouseInfo getMouseInfo()
{
return currentData.getMouseInfo();
}
| The mouse got clicked at the given world location
| @param x The pixel location in the world (not cells)
| @param y The pixel location in the world (not cells)
| @param button The button reported by the original event.
| @param clickCount The click count recorded by the original event.
|
@OnThread(Tag.Any)
public void mouseClicked(int x, int y, MouseButton button, int clickCount)
{
if (locator == null)
{
return;
}
synchronized (this)
{
MouseEventData mouseData = futureData;
if (futureData.isMouseDragEnded())
{
mouseData = potentialNewDragData;
}
if (!PriorityManager.isHigherPriority(MouseEvent.MOUSE_CLICKED, mouseData))
{
return;
}
registerEventRecieved();
int tx = locator.getTranslatedX(x);
int ty = locator.getTranslatedY(y);
mouseData.mouseClicked(tx, ty, x, y, getButton(button), clickCount);
isDragging = false;
}
}
| Translates a JavaFX button to 1/2/3 as used by the Greenfoot API for left/middle/right.
|
private int getButton(MouseButton button)
{
switch (button)
{
case PRIMARY:
return 1;
case MIDDLE:
return 2;
case SECONDARY:
return 3;
default:
return 0;
}
}
| The mouse left the world area.
|
@OnThread(Tag.Any)
public synchronized void mouseExited()
{
futureData.mouseExited();
registerEventRecieved();
}
| The mouse got pressed on the given world location
| @param x The pixel location in the world (not cells)
| @param y The pixel location in the world (not cells)
| @param button The button reported by the original event.
|
@OnThread(Tag.Any)
public void mousePressed(int x, int y, MouseButton button)
{
if (locator == null)
{
return;
}
synchronized(this)
{
MouseEventData mouseData = futureData;
if (futureData.isMouseDragEnded())
{
mouseData = potentialNewDragData;
}
dragStartData = new MouseEventData();
int tx = locator.getTranslatedX(x);
int ty = locator.getTranslatedY(y);
dragStartData.mousePressed(tx, ty, x, y, getButton(button));
gotNewDragStartEvent = true;
if (!PriorityManager.isHigherPriority(MouseEvent.MOUSE_PRESSED, mouseData))
{
return;
}
registerEventRecieved();
mouseData.mousePressed(tx, ty, x, y, getButton(button));
isDragging = false;
}
}
| The mouse got released at the given world location
| @param x The pixel location in the world (not cells)
| @param y The pixel location in the world (not cells)
| @param button The button reported by the original event.
|
@OnThread(Tag.Any)
public void mouseReleased(int x, int y, MouseButton button)
{
if (locator == null)
{
return;
}
synchronized(this)
{
if (isDragging)
{
if (futureData.isMouseDragEnded())
{
futureData = potentialNewDragData;
}
if (!PriorityManager.isHigherPriority(MouseEvent.MOUSE_RELEASED, futureData))
{
return;
}
registerEventRecieved();
int tx = locator.getTranslatedX(x);
int ty = locator.getTranslatedY(y);
futureData.mouseClicked(tx, ty, x, y, getButton(button), 1);
futureData.mouseDragEnded(tx, ty, x, y, getButton(button), dragStartData);
isDragging = false;
potentialNewDragData = new MouseEventData();
}
}
}
| The mouse got dragged to the given world location
| @param x The pixel location in the world (not cells)
| @param y The pixel location in the world (not cells)
| @param button The button reported by the original event.
|
@OnThread(Tag.Any)
public void mouseDragged(int x, int y, MouseButton button)
{
if (locator == null)
{
return;
}
synchronized(this)
{
isDragging = true;
if (!PriorityManager.isHigherPriority(MouseEvent.MOUSE_DRAGGED, futureData))
{
return;
}
registerEventRecieved();
int tx = locator.getTranslatedX(x);
int ty = locator.getTranslatedY(y);
futureData.mouseDragged(tx, ty, x, y, dragStartData.getButton(), dragStartData.getActor());
}
}
| The mouse got moved to the given world location
| @param x The pixel location in the world (not cells)
| @param y The pixel location in the world (not cells)
|
@OnThread(Tag.Any)
public void mouseMoved(int x, int y)
{
if (locator == null)
{
return;
}
synchronized(this)
{
if (!PriorityManager.isHigherPriority(MouseEvent.MOUSE_MOVED, futureData))
{
return;
}
registerEventRecieved();
int tx = locator.getTranslatedX(x);
int ty = locator.getTranslatedY(y);
futureData.mouseMoved(tx, ty, x, y);
isDragging = false;
}
}
| Called when the world starts running, to discard any
| old mouse data that may have been accumulated while paused.
|
public void startedRunning()
{
futureData = new MouseEventData();
}
}
top,
use,
map,
class MousePollingManager
. MousePollingManager
. setWorldLocator
. newActStarted
. registerEventRecieved
. isMousePressed
. isMouseClicked
. isMouseDragged
. isMouseDragEnded
. isMouseMoved
. getMouseInfo
. mouseClicked
. getButton
. mouseExited
. mousePressed
. mouseReleased
. mouseDragged
. mouseMoved
. startedRunning
412 neLoCode
+ 163 LoComm