/*
* Engine Alpha ist eine anfängerorientierte 2D-Gaming Engine.
*
* Copyright (c) 2011 - 2014 Michael Andonie and contributors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package ea;
import ea.internal.gui.Fenster;
import ea.internal.util.Logger;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
/**
* Klasse zur Kontrolle der Maus
*
* @author Michael Andonie, Niklas Keller <me@kelunik.com>
*/
public class Maus {
/* -------------------- Konstanten -------------------- */
/**
* Standard-Maus-Zeiger der Engine Alpha
*/
public static final int TYPE_STANDARD = 0;
/**
* Ein Fadenkreuz
*/
public static final int TYPE_FADENKREUZ = 1;
/**
* Ein klassischer Zeiger, der eine Hand darstellt mit einem Zeigefinger
*/
public static final int TYPE_HAND = 2;
/**
* Ein klassischer Zeiger, wie unter Windows gewohnt
*/
public static final int TYPE_KLASSIK = 3;
/* -------------------- /Konstanten -------------------- */
/**
* Die Art der Maus.
* <p/>
* Eine Maus kann ein klassischer Pfeil, ein Textbearbeitungszeichen, eine Sanduhr, ein
* Fadenkreuz oder jedes andere beliebiges Raum-Objekt sein. Gerade kreative Mäuse werden für
* Spiele verwendet.
* <p/>
* Die Liste aller Mausarten ist im Konstruktor sowie im <b>Handbuch</b> festgehalten.
*/
private final int type;
/**
* Gibt an, ob die Maus fixiert ist oder nicht.
* <p/>
* In diesem Fall ist der Mauszeiger immer Mittelpunkt des Bildes, das Bild bewegt sich dann
* immer gemäß der Mausbewegung.
*/
private final boolean fixed;
/**
* Gibt an, ob die Maus den angezeigten Bildschirmbereich verschieben kann.
*/
private final boolean bewegend;
/**
* Die Liste aller Raum-Klick-Auftraege
*/
private final ArrayList<Auftrag> mausListe = new ArrayList<>();
/**
* Eine Liste aller angemeldeten KlickReagierbar Objekte.
*/
private final ArrayList<KlickReagierbar> klickListeners = new ArrayList<>();
/**
* Eine Liste aller angemeldeten RechtsKlickReagierbar Objekte.
*/
private final ArrayList<RechtsKlickReagierbar> rechtsKlickListeners = new ArrayList<>();
/**
* Eine Liste aller angemeldeten MausLosgelassenReagierbar Objekte.
*/
private final ArrayList<MausLosgelassenReagierbar> mausLosgelassenListeners = new ArrayList<>();
/**
* Eine Liste aller angemeldeten MausBewegungReagierbar Objekte.
*/
private final ArrayList<MausBewegungReagierbar> mausBewegungListeners = new ArrayList<>();
/**
* Das Mausbild. Wichtig bei einer individuellen Maus.
*/
private final Raum bild;
/**
* Der individuelle Hotspot
*/
private final Punkt hotspot;
/**
* Das Fenster, in dem diese Maus aktiv ist. <code>null</code>, falls es kein solches Fenster
* gibt.
*/
private Fenster fenster;
/**
* Dieser Konstruktor ist lediglich eine Vereinfachung. Siehe Dokumentation des vollständigen
* Konstruktors für eine Dokumentation der Parameter: {@link #Maus(Raum, Punkt, boolean,
* boolean)}
* <p/>
* <code>bewegend</code> wird bei diesem Konstruktor auf <code>false</code> gesetzt.
*
* @param mausbild
* siehe {@link #Maus(Raum, Punkt, boolean, boolean)}
* @param hotspot
* siehe {@link #Maus(Raum, Punkt, boolean, boolean)}
* @param fixed
* siehe {@link #Maus(Raum, Punkt, boolean, boolean)}
*
* @see #Maus(Raum, Punkt, boolean, boolean)
* @see Bild
* @see Kamera
*/
@API
public Maus (Raum mausbild, Punkt hotspot, boolean fixed) {
this(mausbild, hotspot, fixed, false);
}
/**
* Dieser Konstruktor ermöglicht es, ein eigenes Mausbild einzubringen.
* <p/>
* Hierfür muss klar sein, was ein Hotspot ist:<br> Ein Hotspot ist ein Punkt, relativ entfernt
* von der linken oberen Ecke des Mausbildes. Dieser Punkt ist der eigentliche Klickpunkt.
* <p/>
* Angenommen das Mausbild misst 20 x 20 Pixel und der Hotspot ist ein Punkt mit den Koordinaten
* (10|10), so wäre der Hotspot in der Mitte des Bildes und bei einem Klick wären diese
* Klick-Koordinaten genau in der Mitte. Wäre der Hotspot (0|0), so wäre der Hotspot genau die
* linke obere Ecke des Mausbildes.
*
* @param mausbild
* Das Objekt, das ab sofort das Mausbild sein wird und auch dementsprechend bewegt wird.
* @param hotspot
* Der bereits beschriebene Hotspot
* @param fixed
* Ob diese Maus fixiert sein soll: Ist dieser Wert <code>true</code>, so ist die Maus immer
* in der Mitte. Bei <code>false</code> verhält sich diese Maus genauso wie gewohnt.
* @param bewegend
* Regelt, ob die Kamera bei Mausbewegungen bewegt wird.
* <p/>
* Bei <code>true</code>: <ul> <li>Falls die Maus fixiert ist, bleibt sie weiterhin in der
* Mitte und die Kamera wird bei jeder Bewegung bewegt.</li> <li>Falls die Maus nicht fixiert
* ist, so wird die Kamera erst bewegt, wenn die Maus den Rand des Fensters beführt.</li>
* </ul>
* <p/>
* Bei <code>false</code> wird die Kamera nie automatisch bewegt. Allerdings ist dies über das
* Interface {@link ea.MausBewegungReagierbar} manuell realisierbar. Ohne dieses Interface
* gibt <code>false</code> bei einer fixierten Maus wenig Sinn.
*
* @see ea.Bild
* @see ea.Kamera
*/
@API
public Maus (Raum mausbild, Punkt hotspot, boolean fixed, boolean bewegend) {
this.type = -1;
this.bild = mausbild;
this.hotspot = hotspot;
this.fixed = fixed;
this.bewegend = bewegend;
}
/**
* Erstellt eine Maus, die die Kamera nicht bewegt und nicht fixiert ist.
* <p/>
* Weitere Erläuterungen: {@link #Maus(Raum, Punkt, boolean, boolean)}
*
* @param mausbild
* siehe {@link #Maus(Raum, Punkt, boolean, boolean)}
* @param hotspot
* siehe {@link #Maus(Raum, Punkt, boolean, boolean)}
*
* @see #Maus(Raum, Punkt, boolean)
*/
@API
public Maus (Raum mausbild, Punkt hotspot) {
this(mausbild, hotspot, false, false);
}
/**
* Vereinfachter Konstruktor für eine Standard-Maus.
* <p/>
* Hierbei gibt es verschiedene Mäuse, die über ihren Index gewählt werden können. Die
* verlinkten Konstanten sollten verständlich dokumentiert sein. Ansonsten ist an dieser Stelle
* auch das Wiki hilfreich.
* <p/>
* Die Maus ist dabei weder fixiert noch wird die Kamera durch ihre Bewegung bewegt.
*
* @param type
* Die Art der Maus. Jeder Wert steht für eine andere Maus.
* <p/>
* {@link #TYPE_STANDARD}, {@link #TYPE_FADENKREUZ}, {@link #TYPE_HAND} oder {@link
* #TYPE_KLASSIK}
*/
@API
public Maus (int type) {
this(type, false, false);
}
/**
* Vollständiger Konstruktor für eine Standard-Maus.
* <p/>
* Hierbei gibt es verschiedene Mäuse, die über ihren Index gewählt werden können. Die
* verlinkten Konstanten sollten verständlich dokumentiert sein. Ansonsten ist an dieser Stelle
* auch das Wiki hilfreich.
*
* @param type
* Die Art der Maus. Jeder Wert steht für eine andere Maus.
* <p/>
* {@link #TYPE_STANDARD}, {@link #TYPE_FADENKREUZ}, {@link #TYPE_HAND} oder {@link
* #TYPE_KLASSIK}
* @param fixed
* Ob diese Maus fixiert sein soll: Ist dieser Wert <code>true</code>, so ist die Maus immer
* in der Mitte. Bei <code>false</code> verhält sich diese Maus genauso wie gewohnt.
* @param bewegend
* Ob die Maus die Kamera bewegen koennen soll oder nicht.
*/
@API
public Maus (int type, boolean fixed, boolean bewegend) {
this.type = type;
this.fixed = fixed;
this.bewegend = bewegend;
this.bild = getImage();
this.hotspot = hotSpot();
}
/**
* <b>Berechnet</b> das Bild von der Maus, das in der Engine dargestellt wird.
*
* @return Das Bild des Mauscursors.
*/
public Raum getImage () {
if (bild != null) {
return bild;
}
BufferedImage ret = null;
String verzeichnis = "/assets/mouse/"; // Das Verzeichnis der Mauskomponenten
switch (type) {
case TYPE_STANDARD:
verzeichnis += "blau.gif";
break;
case TYPE_FADENKREUZ:
verzeichnis += "fadenkreuz.gif";
break;
case TYPE_HAND:
verzeichnis += "hand.gif";
break;
case TYPE_KLASSIK:
verzeichnis += "klassisch.gif";
break;
default:
// TODO Die Datei existierte nicht, gibt es da noch einzweites Fadenkreuz?
verzeichnis += "fadenkreuz.gif";
break;
}
InputStream in = Maus.class.getResourceAsStream(verzeichnis);
try {
ret = ImageIO.read(in);
} catch (IOException ex) {
Logger.error("Achtung! Das zu ladende Standard-Mausbild konnte nicht geladen werden.");
}
return new Bild(0, 0, ret);
}
/**
* @return der Punkt auf dem Mausbild, der als Hotspot fungiert.
*/
public Punkt hotSpot () {
if (hotspot != null) {
return hotspot;
}
int x = 0, y = 0;
switch (type) {
case TYPE_FADENKREUZ:
x = y = 12;
break;
case TYPE_HAND:
x = 5;
y = 0;
break;
case 3: // Hotspot bleibt auf (0|0)
default:
break;
}
return new Punkt(x, y);
}
/**
* Setzt die Referenz auf das Fenster, in dem diese Maus sitzt, neu. <b>ACHTUNG:</b> Sollte
* nicht von Außen benutzt werden, falls man sich nicht genau mit der Struktur der Engine
* auskennt.
*
* @param f
* Die neue Fensterreferenz.
*/
public void fensterSetzen (Fenster f) {
this.fenster = f;
}
/**
* Alternativmethopde zum anmelden. Hierbei gibt es keinen Code-Parameter; dieser wird
* automatisch auf <code>0</code> gesetzt.
*
* @param m
* Der anzumeldende Listener
* @param objekt
* Das zu auf Klicks zu ueberwachende Objekt
*
* @see #anmelden(MausReagierbar, Raum, int)
*/
public void mausReagierbarAnmelden (MausReagierbar m, Raum objekt) {
this.anmelden(m, objekt, 0);
}
/**
* Meldet ein Raumobjekt zum Ueberwachen an einem bestimmten Listener an, unter Eingabe eines
* bestimmten Codes.
*
* @param m
* Das Listener-Objekt, dass bei Kollision benachrichtigt werden soll.
* @param objekt
* Das auf Klick zu ueberwachende Raum-Objekt
* @param code
* Der Code, mit dem ein Klick auf dieses Objekt assoziiert wird.<br /> Diese Zahl wird als
* Argument bei der Reaktionsmethode mitgegeben, wodurch, bei korrekten
* Programmierorganisation dieser Zahlen, eine sehr gute Unterscheidung ohne groesseren
* Methodenaufwand noetig ist.
*
* @see MausReagierbar#mausReagieren(int)
*/
public void anmelden (MausReagierbar m, Raum objekt, int code) {
mausListe.add(new Auftrag(m, code, objekt));
}
/**
* Uebernimmt alle Listener von einer anderen Maus.
*
* @param von
* Von dieser Maus, werden alle Listener-Listen übernommen. Bereits vorhandene Listener
* bleiben dabei erhalten, werden aber eventuell <b>DOPPELT</b> eingefügt.
*/
public void uebernehmeAlleListener (Maus von) {
for (Auftrag a : von.mausListe) {
this.anmelden(a.listener, a.koll, a.signal);
}
for (KlickReagierbar kr : von.klickListeners) {
this.klickReagierbarAnmelden(kr);
}
for (RechtsKlickReagierbar rkr : von.rechtsKlickListeners) {
this.rechtsKlickReagierbarAnmelden(rkr);
}
for (MausLosgelassenReagierbar mlr : mausLosgelassenListeners) {
this.mausLosgelassenReagierbarAnmelden(mlr);
}
for (MausBewegungReagierbar mbr : mausBewegungListeners) {
this.mausBewegungReagierbarAnmelden(mbr);
}
}
/**
* Meldet ein KlickReagierbar bei der Maus an.<br /> Ab dann wird es bei jedem Mausklick
* (Linksklick) in der Engine benachrichtigt.
*
* @param k
* Das anzumeldende KlickReagierbar-Interface
*/
public void klickReagierbarAnmelden (KlickReagierbar k) {
klickListeners.add(k);
}
/**
* Meldet ein RechtsKlickReagierbar-Objekt bei der Maus an.<br /> Ab dann wird es bei jedem
* Rechtsklick benachrichtigt.
*
* @param k
* Das anzumeldende <code>RechtsKlickReagierbar</code>-Interface
*/
public void rechtsKlickReagierbarAnmelden (RechtsKlickReagierbar k) {
rechtsKlickListeners.add(k);
}
/**
* Meldet ein <code>MausLosgelassenReagierbar</code>-Objekt bei der Maus an. <br /> Ab dann wird
* es jedes mal durch Aufruf seiner Methode benachrichtigt, wenn eine Maustaste losgelassen
* wird.
*
* @param m
* Listener-Objekt
*/
public void mausLosgelassenReagierbarAnmelden (MausLosgelassenReagierbar m) {
mausLosgelassenListeners.add(m);
}
/**
* Meldet ein <code>MausBewegungReagierbar</code>-Objekt bei der Maus an. <br /> Ab dann wird es
* jedes mal durch Aufruf seiner Methode benachrichtigt, wenn die Maus bewegt wird.
*
* @param m
* Listener-Objekt
*/
public void mausBewegungReagierbarAnmelden (MausBewegungReagierbar m) {
mausBewegungListeners.add(m);
}
// TODO NotUsed. Soll das für bewegt und klick simuliert werden können?
// Ansonsten muss mit @NoExternalUse annotiert werden.
/**
* Bei einer angemeldeten Maus wird bei einem Klick diese Methode aufgerufen.<br /> Theoretisch
* liessen sich so Tastenklicks "simulieren".
*
* @param p der respektive Punkt für den simulierten Mausklick.
* @param links
* War der Klick ein Linksklick, ist dieser Wert <code>true</code>. Fuer jede andere Klickart
* ist dieser Wert <code>false</code>. In diesem Fall wird mit einem Rechtsklick gerechnet.
*
* @see ea.Maus#klick(int, int, boolean, boolean)
*/
public void klick (Punkt p, boolean links) {
this.klick(p, links, false);
}
/**
* Bei einer angemeldeten Maus wird bei einem Klick diese Methode aufgerufen.<br /> So lassen
* sich auch Klicks auf die Maus "simulieren".
*
* @param p der Punkt des Klicks
* @param links
* War der Klick ein Linksklick, ist dieser Wert <code>true</code>. Fuer jede andere Klickart
* ist dieser Wert <code>false</code>. In diesem Fall wird mit einem Rechtsklick gerechnet.
* @param losgelassen
* ist dieser Wert <code>true</code>, so wird dies als losgelassene Taste behandelt.
*/
public void klick (Punkt p, boolean links, boolean losgelassen) {
p = p.verschobeneInstanz(hotspot.alsVektor());
if (losgelassen) {
for (MausLosgelassenReagierbar m : mausLosgelassenListeners) {
m.mausLosgelassen(p, links);
}
return;
}
if (links) {
for (Auftrag a : mausListe) {
a.klick(p);
}
for (KlickReagierbar k : klickListeners) {
k.klickReagieren(p);
}
} else {
for (RechtsKlickReagierbar k : rechtsKlickListeners) {
k.rechtsKlickReagieren(p);
}
}
}
/**
* Benachrichtigt alle Listener, falls die Bewegung nicht (0|0) war.
*
* @param bewegung der Vektor, der die Bewegung der Maus beschreibt.
*/
public void bewegt (Vektor bewegung) {
if (bewegung.unwirksam()) {
return;
}
for (MausBewegungReagierbar m : mausBewegungListeners) {
m.mausBewegt(bewegung);
}
}
/**
* Entfernt ein bestimmtes MausReagierbar-Interface <b>gaenzlich</b> von Kollisionstest.<br />
* Heisst das also, dass mehrere verschiedene Raum-Objekte an dem uebergebenen Objekt Ueberwacht
* werden, so wird ab sofort fuer keines mehr eine Benachrichtigung stattfinden.
*
* @param g
* Das gaenzlich von Klick-Tests zu entfernende <code>MausReagierbar</code>-Interface
*/
public void entfernen (MausReagierbar g) {
ArrayList<Auftrag> l = new ArrayList<>();
for (Auftrag a : mausListe) {
if (a.benachrichtigt(g)) {
l.add(a);
}
}
for (Auftrag a : l) {
mausListe.remove(a);
}
}
/**
* @return Ob die Maus den Bildschirm bewegen kann
*/
public boolean bewegend () {
return this.bewegend;
}
/**
* Gibt den <i>Punkt auf der Zeichenebene</i> aus, auf den die Maus bei einem Klick zeigen
* würde. Diese Methode rechnet alle Umstände der Maus (z.B. relativ bzw. fixed) mit ein und
* gibt die genaue Position des Klicks zurück.
*
* @return Der genaue Punkt auf der Zeichenebene, auf den diese Maus bei einem Klick deuten
* würde.
*/
public Punkt klickAufZeichenebene () {
if (absolut()) {
BoundingRechteck r = bild.dimension();
Punkt p = hotSpot();
return new Punkt((int) (r.x + p.realX() + fenster.getCam().getX()), (int) (r.y + p.realY() + fenster.getCam().getY())); // Mit
// zurückrechnen
// auf die
// Bildebene!
} else {
//Fenster Dimension
Dimension dim = fenster.getSize();
int startX = (dim.width / 2);
int startY = (dim.height / 2);
return new Punkt(startX + fenster.getCam().getX(), startY + fenster.getCam().getY());
}
}
/**
* @return Ob die Maus fixed ist oder nicht.
*/
public boolean absolut () {
return this.fixed;
}
/**
* Gibt die Liste aller Auftraege (interne Klasse!!) fuer MausReagierbar- Interfaces <br/> <br
* /> <b>ACHTUNG</b> Die ArrayList ist verantwortlich fuer die MausReagierbar-Aktionen. Daher
* stellt die Verwendung dieses Objekts durchaus eine Fehlerquelle dar.
*
* @return die Liste aller MausReagierbar-Auftraege
*
* @see ea.Maus.Auftrag
*/
@SuppressWarnings ( "unused" )
public ArrayList<Auftrag> mausReagierbarListe () {
return mausListe;
}
/**
* Diese Klasse sammelt die Auftraege der KlickReagierbar-Interfaces.
*/
public final class Auftrag { // TODO Wird diese Klasse wirklich genutzt? Für was?
//
/**
* Der Listener
*/
private final MausReagierbar listener;
/**
* Das Kollisionszubeobachtende Raum-Objekt
*/
private final Raum koll;
/**
* Das auszufuehrende Signal
*/
private int signal;
/**
* Konstruktor
*
* @param listen
* Der Listener
* @param signal
* Der Signalcode
* @param m
* Das Kollisionsobjekt
*/
public Auftrag (MausReagierbar listen, int signal, Raum m) {
listener = listen;
this.signal = signal;
koll = m;
}
/**
* Setzt den Wert des Signals, das beim Klick auf das Ziel ausgeloest wird neu.
*
* @param signal
* Das neue Signal
*/
@SuppressWarnings ( "unused" )
public void signalSetzen (int signal) {
this.signal = signal;
}
/**
* Uebertrag des Klick auf den Auftrag
*
* @param p
* Der Klickpunkt
*/
public void klick (Punkt p) {
if (koll.beinhaltet(p)) {
listener.mausReagieren(signal);
}
}
/**
* @return TRUE, wenn dieser Listener benachrichtigt wird
*/
public boolean benachrichtigt (MausReagierbar r) {
return r == listener;
}
/**
* @return <code>true</code>, wenn das Objekt beobachtet wird, sonst <code>false</code>.
*/
@SuppressWarnings ( "unused" )
public boolean beobachtet (Raum m) {
return m == koll;
}
/**
* Gibt das Signal zurück.
*
* @return Das Signal, das beim Klick auf das Zielobjekt gesendet wird.
*/
@SuppressWarnings ( "unused" )
public int signal () {
return signal;
}
}
}