/* * 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.internal.gui; import ea.*; import ea.internal.gra.Zeichenebene; import ea.internal.gra.Zeichner; import ea.internal.util.Logger; import java.awt.*; import java.awt.event.*; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.List; /** * Das Fenster als Oberfenster.<br /> In ihm fängt sich die Maus, sie kann also das Fenster nicht * mehr nach dem Betreten verlassen. * * @author Michael Andonie, Niklas Keller <me@kelunik.com> */ public class Fenster extends Frame { private static final long serialVersionUID = 1L; /** * Statische Hilfsinstanz zur Vereinfachung der frameabhängigen Abfragen */ public static Fenster instanz = null; /** * Counter, der die Anzahl der effektiv vorhandenen Frames zählt. */ private static volatile int frameCount = 0; /** * Gibt an, ob das Fenster im Vollbildmodus arbeitet */ private final boolean vollbild; /** * Das Panel, das fuer die Zeichenroutine verantwortlich ist. */ private final Zeichner zeichner; /** * Die Liste aller TastenListener. */ private final java.util.List<TastenReagierbar> listener = new ArrayList<>(); /** * Die Liste aller TastenLosgelassen-Listener */ private final List<TastenLosgelassenReagierbar> losListener = new ArrayList<>(); /** * Gibt an, ob die aktuelle (relative) Maus innerhalb des passablen Fensterbereiches liegt.<br * /> Gibt es keine solche ist dieser Wert irrelevant. */ private volatile boolean mausAusBild = false; /** * Gibt an, ob der gerade Verarbeitete Klick mitzählt, also vom Benutzer selbst gemacht wurde. */ private boolean zaehlt = true; /** * Die Maus, die in dem Fenster sichtbar ist.<br /> Ist diese Referenz <code>null</code>, kann * man keine Maus sehen. */ private volatile Maus maus = null; /** * Ein Roboter, der die Maus bei Austritt im Fenster hält. */ private Robot robot; /** * Das Bild der Maus. */ private Raum mausBild; /** * Ein Array als die Tastentabelle, nach der für die gedrueckt-Methoden vorgegangen wird.<br /> * Ist ein Wert <code>true</code>, so ist die Taste dieses Indexes heruntergedrückt. */ private volatile boolean[] tabelle; /** * letzte Maus-Position */ private Point lastMousePosition; /** * Einfacher Alternativkonstruktor.<br /> Erstellt ein normales Fenster mit der eingegeben * Groesse. * * @param x * Die Breite * @param y * Die Hoehe */ public Fenster (int x, int y) { this(x, y, "EngineAlpha - Ein Projekt von Michael Andonie", false, 50, 50); } /** * Konstruktor fuer Objekte der Klasse Fenster. * * @param breite * Die Breite des Fensters. (Bei erfolgreichem Vollbild die neue Standartbildschirmbreite) * @param hoehe * Die Hoehe des Fensters. (Bei erfolgreichem Vollbild die neue Standartbildschirmhoehe) * @param titel * Der Titel, der auf dem Fenster gezeigt wird (Auch bei Vollbild nicht zu sehen).Wenn kein * Titel erwuenscht ist, kann ein leerer String (<code>""</code>) eingegeben werden. * @param vollbild * Ob das Fenster ein echtes Vollbild sein soll, sprich den gesamten Bildschirm ausfuellen * soll und nicht mehr wie ein Fenster aussehen soll.<br /> Es kann sein, dass Vollbild zum * Beispiel aufgrund eines Veralteten Javas oder inkompatiblen PCs nicht moeglich ist, <b>in * diesem Fall wird ein normales Fenster mit den eingegeben Werten erzeugt</b>.<br /> Daher * ist das x/y - Feld eine Pflichteingabe. * @param fensterX * Die X-Koordinate des Fensters auf dem Computerbildschirm. * @param fensterY * Die Y-Koordinate des Fensters auf dem Computerbildschirm. */ public Fenster (int breite, int hoehe, String titel, boolean vollbild, int fensterX, int fensterY) { super(titel); frameCount++; int WINDOW_FRAME = 1; int WINDOW_FULLSCREEN = 2; int WINDOW_FULLSCREEN_FRAME = 4; int windowMode = vollbild ? WINDOW_FULLSCREEN : WINDOW_FRAME; tabelle = new boolean[45]; this.vollbild = vollbild; this.setSize(breite, hoehe); this.setResizable(false); // ------------------------------------- // GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice[] devices = env.getScreenDevices(); Dimension screenSize = getToolkit().getScreenSize(); if (vollbild) { if (devices[0].isFullScreenSupported()) { breite = screenSize.width; hoehe = screenSize.height; } else { Logger.error("Achtung!"); Logger.error("Vollbild war nicht möglich, weil dieser PC dies nicht unterstützt!"); windowMode = WINDOW_FULLSCREEN_FRAME; } } else { int x = screenSize.width / 8, y = screenSize.height / 8; if (fensterX > -1 && fensterX < screenSize.width) { x = fensterX; } if (fensterY > -1 && fensterY < screenSize.height) { y = fensterY; } setLocation(x, y); } // ------------------------------------- // if (windowMode == WINDOW_FULLSCREEN) { setUndecorated(true); devices[0].setFullScreenWindow(this); // Resolution-Check boolean success = false; if (devices[0].isDisplayChangeSupported()) { DisplayMode[] displayMode = devices[0].getDisplayModes(); Logger.info("DisplayModes: " + displayMode.length); for (int i = 0; i < displayMode.length; i++) { Logger.info((i + 1) + ": " + "Breite: " + displayMode[i].getWidth() + ", Höhe: " + displayMode[i].getHeight()); if (displayMode[i].getWidth() == breite && displayMode[i].getHeight() == hoehe) { devices[0].setDisplayMode(new DisplayMode(breite, hoehe, displayMode[i].getBitDepth(), displayMode[i].getRefreshRate())); Logger.info("SET!"); success = true; break; } } if (!success) { Logger.error("Achtung!" + "\n" + "Die angegebene Auflösung wird von diesem Bildschirm nicht unterstützt!" + "\n" + "Nur besondere Auflösungen sind möglich, z.B. 800 x 600." + "\n" + "Diese sollten in der Konsole vor dieser Fehlerausgabe gelistet sein."); } } else { Logger.error("Dieser Bildschirm unterstützt keine Auflösungsänderung!"); } if (!success) { Logger.error("Die gewünschte Auflösung wird nicht vom Hauptbildschirm des Computers unterstützt!"); } } else if (windowMode == WINDOW_FULLSCREEN_FRAME) { setVisible(true); setBounds(env.getMaximumWindowBounds()); setExtendedState(MAXIMIZED_BOTH); Insets insets = getInsets(); breite = getWidth() - insets.left - insets.right; hoehe = getHeight() - insets.top - insets.bottom; } else { setVisible(true); } this.zeichner = new Zeichner(breite, hoehe, new Kamera(breite, hoehe, new Zeichenebene())); this.add(zeichner); this.zeichner.init(); if ((windowMode & (WINDOW_FULLSCREEN_FRAME | WINDOW_FRAME)) > 0) { this.pack(); } // ------------------------------------- // // Der Roboter try { robot = new Robot(devices[0]); } catch (AWTException e) { Logger.error("Achtung!" + "\n" + "Es war nicht möglich ein GUI-Controlobjekt zu erstelllen!" + "\n" + "Zentrale Funktionen der Maus-Interaktion werden nicht funktionieren." + "\n" + "Grund: Dies liegt an diesem Computer."); } // Die Listener addKeyListener(); addMouseListener(); addMouseMotionListener(); addWindowListener(new Adapter(this)); addWindowListener(new WindowAdapter() { @Override public void windowClosing (WindowEvent e) { loeschen(); } }); removeCursor(); // Mausfang Manager.standard.anmelden(new Ticker() { private static final long serialVersionUID = -7552570027596373694L; @Override public void tick () { if (hatMaus() && !maus.absolut() && maus.bewegend()) { try { BoundingRechteck r = mausBild.dimension(); Punkt hs = maus.hotSpot(); BoundingRechteck praeferenz = mausPlatz(); Punkt p = new Punkt(r.x + hs.realX(), r.y + hs.realY()); if (!praeferenz.istIn(p) && maus.bewegend()) { getCam().verschieben((new Vektor(praeferenz.zentrum(), p).teilen(20))); } } catch (NullPointerException e) { // Einfangen der maximal einmaligen RuntimeException zum // sichern // TODO Wann tritt diese Exception auf? Das muss schöner gehen, // wenn sie immer auftritt. } } } }, 50); instanz = this; } private void addKeyListener () { KeyListener keyListener = new KeyListener() { @Override public void keyTyped (KeyEvent e) { } @Override public void keyPressed (KeyEvent e) { tastenAktion(e); } @Override public void keyReleased (KeyEvent e) { int i = zuordnen(e.getKeyCode()); if (i == -1) { return; } tabelle[i] = false; for (TastenLosgelassenReagierbar l : losListener) { l.tasteLosgelassen(i); } } }; addKeyListener(keyListener); zeichner.addKeyListener(keyListener); } private void addMouseListener () { zeichner.addMouseListener(new MouseListener() { @Override public void mouseClicked (MouseEvent e) { } @Override public void mousePressed (MouseEvent e) { mausAktion(e, false); } @Override public void mouseReleased (MouseEvent e) { mausAktion(e, true); } @Override public void mouseEntered (MouseEvent e) { if (hatMaus()) { Point po = getLocation(); int startX = (getWidth() / 2); int startY = (getHeight() / 2); robot.mouseMove(startX + po.x, startY + po.y); } zaehlt = false; robot.mousePress(InputEvent.BUTTON1_MASK); robot.mouseRelease(InputEvent.BUTTON1_MASK); } @Override public void mouseExited (MouseEvent e) { } }); } private void addMouseMotionListener () { zeichner.addMouseMotionListener(new MouseMotionListener() { @Override public void mouseDragged (MouseEvent e) { mausBewegung(e); } @Override public void mouseMoved (MouseEvent e) { mausBewegung(e); } }); } /** * Löscht das Fenster und terminiert damit das Spiel.<br /> <b>Daher nur dann einsetzen, wenn * die Anwendung beendet werden soll!! Der vorherige Zustand ist nicht * wiederherstellbar!!</b><br /> Als alternative Methode zum ausschliesslichen Loeschen des * Fensters steht <code>softLoeschen()</code> zur Verfuegung. */ public void loeschen () { this.zeichner.kill(); this.setVisible(false); this.dispose(); frameCount--; if (frameCount == 0) System.exit(0); } private void removeCursor () { mausLoeschen(); } /** * Testet, ob eine Maus im Spiel vorhanden ist. * * @return TRUE, wenn eine Maus im Spiel ist. */ public boolean hatMaus () { return (maus != null); } /** * @return Das BoundingRechteck, dass den Spielraum der Maus ohne Einbezug der Relativen * Koordinaten (Kameraposition)<br /> Das Rechteck ist die Masse des Fensters mal 3/4.<br /> * Dies ist natuerlich nur dann im Fenster gebraucht, wenn eine relative Maus angemeldet ist. */ private BoundingRechteck mausPlatz () { Dimension d = this.getSize(); int x = (d.width / 4) * 3; int y = (d.height / 4) * 3; return new BoundingRechteck((d.width / 8), (d.height / 8), x, y); } /** * @return Die Kamera, passend zu diesem Fenster */ public Kamera getCam () { return zeichner.cam(); } /** * Die Listener-Methode, die vom Fenster selber bei jeder gedrueckten Taste aktiviert wird.<br * /> Hiebei wird die Zuordnung zu einer Zahl gemacht, und diese dann an alle Listener * weitergereicht, sofern die Taste innerhalb der Kennung des Fensters liegt.<br /> Hierzu: Die * Liste der Tasten mit Zuordnung zu einem Buchstaben; sie ist im <b>Handbuch</b> festgehalten. * * @param e * Das ausgeloeste KeyEvent zur Weiterverarbeitung. */ private void tastenAktion (KeyEvent e) { int z = zuordnen(e.getKeyCode()); if (z == -1 || tabelle[z]) { return; } for (TastenReagierbar r : listener) { r.reagieren(z); } tabelle[z] = true; } /** * Ordnet vom Java-KeyCode-System in das EA-System um. * <p/> * Seit Version 3.0.3 ersetzt durch {@link ea.Taste#vonJava(int)}. * * @param keyCode * Der Java-KeyCode * * @return Entsprechender EA-KeyCode oder <code>-1</code>, falls es keinen passenden EA-KeyCode * gibt. * * @deprecated Seit v3.0.3. Durch {@link ea.Taste#vonJava(int)} ersetzt. */ @Deprecated public int zuordnen (int keyCode) { return Taste.vonJava(keyCode); } /** * Diese Methode wird immer dann ausgeführt, wenn ein einfacher Linksklick der Maus ausgeführt * wird. * * @param e * Das MausEvent * * @param losgelassen Ist dieser Wert <code>true</code>, wurde die Maus eigentlich losgelassen und nicht * geklickt. */ private void mausAktion (MouseEvent e, boolean losgelassen) { if (!zaehlt) { zaehlt = true; return; } // Linksklick? 1: Links - 2: Mausrad? - 3: Rechts final boolean links = e.getButton() != MouseEvent.BUTTON3; if (hatMaus()) { if (maus.absolut() || maus.bewegend()) { Punkt pu = maus.klickAufZeichenebene(); maus.klick(pu, links, losgelassen); } else { // FIXME REVIEW BUG //maus.klick(maus.getImage().positionX() + getCam().getX(), maus.getImage().positionY() + getCam().getY(), links, losgelassen); maus.klick(maus.getImage().position().verschobeneInstanz(getCam().position().position().alsVektor()), links, losgelassen); } } } /** * Diese Methode wird ausgefuehrt, wenn die Maus bewegt wird. * * @param e * Das ausloesende Event */ private void mausBewegung (MouseEvent e) { if (hatMaus()) { Insets insets = this.getInsets(); float centerX = zeichner.getWidth() / 2; float centerY = zeichner.getHeight() / 2; Point windowLocation = getLocation(); Point mousePosition = e.getPoint(); if (maus.absolut()) { if (maus.bewegend()) { Vektor offset = new Vektor(mousePosition.x - centerX, mousePosition.y - centerY); getCam().verschieben(offset); } if (lastMousePosition != null) { maus.bewegt(new Vektor(mousePosition.x - lastMousePosition.x, mousePosition.y - lastMousePosition.y)); } } else { float dx = mousePosition.x - centerX; float dy = mousePosition.y - centerY; BoundingRechteck bounds = mausBild.dimension(); Punkt hotspot = maus.hotSpot(); Punkt hotspotX = new Punkt(bounds.x + hotspot.realX() + dx, bounds.y + hotspot.realY()); Punkt hotspotY = new Punkt(bounds.x + hotspot.realX(), bounds.y + hotspot.realY() + dy); // FIXME Maus bis zum Rand bewegen, aber nicht hinaus. // Maus bewegt sich nicht direkt an den Rand, wenn die Bewegung größer als der // Abstand zum Rand ist! if (!zeichner.masse().istIn(hotspotX)) { dx = 0; } if (!zeichner.masse().istIn(hotspotY)) { dy = 0; } Vektor bewegung = new Vektor(dx, dy); mausBild.verschieben(bewegung); maus.bewegt(bewegung); } robot.mouseMove((int)(windowLocation.x + insets.left + centerX), (int)(windowLocation.y + insets.top + centerY)); lastMousePosition = new Point((int)centerX, (int)centerY); } } /** * Loescht das Maus-Objekt des Fensters.<br /> Hatte das Fenster keine, ergibt sich selbstredend * keine Aenderung. */ public void mausLoeschen () { this.setCursor(getToolkit().createCustomCursor(new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB), new Point(0, 0), "NOCURSOR")); maus = null; } /** * Statische Methode zum Oeffentlichen Berechnen der Fontmetriken des offenen Fensters. * * @param f * Der zu ueberpruefende Font * * @return Das zu dem Font und aktiven Fenster gehoerende FontMetrics-Objekt */ public static FontMetrics metrik (Font f) { return instanz.getFontMetrics(f); } /** * Gibt die aktuellste Instanz dieser KLasse wieder. * * @return Das aktuellste Fenster */ public static Fenster instanz () { return instanz; } /** * Minimiert das Fenster (bringt es in die Taskleiste). */ public void minimieren () { setState(ICONIFIED); } /** * Maximiert das Fenster (bringt es aus der Taskleiste wieder auf den Bildschirm) */ public void maximieren () { setState(NORMAL); } /** * Gibt zurueck, ob dieses Fenster ein Vollbild ist oder nicht. * * @return <code>true</code>, wenn das Fenster ein Vollbild ist, sonst <code>false</code>. */ public boolean vollbild () { return vollbild; } /** * Meldet den hintergrund dieses Fensters und damit des Spiels an.<br /> Gibt es bereits einen, * so wird dieser fortan nicht mehr gezeichnet, dafuer nun dieser. Sollten mehrere Objekte * erwuenscht sein, gezeichnet zu werden, so empfiehlt es sich, diese in einem * <code>Knoten</code>-Objekt zu sammeln und dann anzumelden.<br /> <b>Achtung!</b><br /> Diese * Objekte sollten nicht an der Physik angemeldet werden, dies fuehrt natuerlich zu ungewollten * Problemen! * * @param hintergrund * Der anzumeldende Hintergrund */ public void hintergrundAnmelden (Raum hintergrund) { zeichner.hintergrundAnmelden(hintergrund); } /** * Meldet einen TastenReagierbar - Listener an. * * @param t * Der neu anzumeldende Listener. */ public void anmelden (TastenReagierbar t) { if (t == null) { throw new IllegalArgumentException("Listener darf nicht NULL sein."); } listener.add(t); } /** * Meldet einen TastenLosgelassenReagierbar-Listener an als exakt parallele Methode zu * <code>tastenLosgelassenAnmelden()</code>, jedoch eben ein etwas laengerer, aber vielleicht * auch logischerer Name; fuehrt jedoch exakt die selbe Methode aus!<br /> * <b>!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!ACHTUNG!!!!!!!!!!!!!!!!!!!!!!!! * !!!!!!!!!!!!!!!</b><br /> <code>TastenReagierbar</code> und <code>TastenLosgelassenReagierbar</code> * sind 2 vollkommen unterschiedliche Interfaces! Das eine wird beim Runterdruecken, das andere * beim Loslassen der Tasten aktiviert. * * @see #tastenLosgelassenAnmelden(TastenLosgelassenReagierbar) */ public void tastenLosgelassenReagierbarAnmelden (TastenLosgelassenReagierbar t) { this.tastenLosgelassenAnmelden(t); } /** * Meldet einen TastenLosgelassenReagierbar-Listener an.<br /> <b>!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!ACHTUNG!!!!!!!!!!!!!!!!!!!!!!!! * !!!!!!!!!!!!!!!</b><br /> TastenReagierbar und TastenLosgelassenReagierbar sind 2 vollkommen * unterschiedliche Interfaces! Das eine wird beim Runterdruecken, das andere beim Loslassen der * Tasten aktiviert. */ public void tastenLosgelassenAnmelden (TastenLosgelassenReagierbar t) { if (t == null) { throw new IllegalArgumentException("Listener darf nicht NULL sein."); } losListener.add(t); } /** * Meldet eine Maus an.<br /> Im Gegensatz zu den TastenReagierbar-Listenern kann nur eine Maus * am Fenster angemeldet sein. * * @param m * Die anzumeldende Maus */ public void anmelden (Maus m) { if (hatMaus()) { Logger.error("Es ist bereits eine Maus angemeldet!"); } else { maus = m; maus.fensterSetzen(this); BoundingRechteck r = maus.getImage().dimension(); maus.getImage().positionSetzen(((getWidth() - r.breite) / 2), (getHeight() - r.hoehe) / 2); mausBild = maus.getImage(); zeichner.anmelden(mausBild); } } /** * Gibt an, ob die Maus den Bildschirm bewegen kann. * * @return TRUE, wenn die Maus den Bildschirm bewegen kann, FALSE, wenn nicht.<br /> Ist keine * Maus angemeldet, ist das Ergebnis ebenfalls FALSE. */ public boolean mausBewegt () { return hatMaus() && maus.bewegend(); } /** * @return Der Statische Basisknoten. */ public Knoten getStatNode () { return zeichner.statNode(); } /** * Gibt die Maus aus. * * @return Die aktuelle Maus. Kann <code>null</code> sein!! */ public Maus getMaus () { return maus; } /** * Gibt die Fenstermasse in einem BoundingRechteck an. * * @return ein BoundingRechteck mit Position (0|0), dessen Hoehe & Breite die Masse des Fensters * in Pixel angeben. * * @see ea.BoundingRechteck */ public BoundingRechteck fenstermasse () { return zeichner.masse(); } /** * Gibt den Zeichner des Fensters aus. * * @return Der Zeichner des Fensters. */ public Zeichner zeichner () { return zeichner; } /** * Entfernt ein simples Grafikobjekt. * * @param g * Das darzustellende Grafikobjekt */ public void removeSimple (SimpleGraphic g) { zeichner.removeSimple(g); } /** * Fuellt ein simples Grafikobjekt in die anzeige. * * @param g * Das darzustellende Grafikobjekt. */ public void fillSimple (SimpleGraphic g) { zeichner.addSimple(g); } /** * Gibt das gespeicherte Bild-Objekt der Maus wieder. * * @return Das Bild mit seiner Position und Groesse von der Maus. */ public Raum mausBild () { return this.mausBild; } /** * Deaktiviert den eventuell vorhandenen gemerkten Druck auf allen Tasten.<br /> Wird innerhalb * der Engine benutzt, sobald das Fenster deaktiviert etc. wurde. */ public void druckAufheben () { for (int i = 0; i < tabelle.length; i++) { tabelle[i] = false; } } /** * Überprüft, ob eine bestimmte Taste auf der Tastatur heruntergedrückt wurde. * * @param tastencode * Der Code der Taste, für die getestet werden soll. * * @return <code>true</code>, falls die entsprechende Taste gerade heruntergedrückt wurde, sonst * <code>false</code>. * * @throws java.lang.ArrayIndexOutOfBoundsException * Sollte einen Zahl < 0 oder > 44 verwendet werden. */ public boolean istGedrueckt (int tastencode) { return tabelle[tastencode]; } }