/******************************************************************************* * Copyright (c) 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *******************************************************************************/ package go.graphics.area; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import go.graphics.GLDrawContext; import go.graphics.RedrawListener; import go.graphics.UIPoint; import go.graphics.event.GOEvent; import go.graphics.event.GOEventHandlerProvider; import go.graphics.event.GOKeyEvent; import go.graphics.event.GOModalEventHandler; import go.graphics.event.command.GOCommandEvent; import go.graphics.event.command.GOCommandEventProxy; import go.graphics.event.interpreter.AbstractMouseEvent; import go.graphics.event.mouse.GODrawEvent; import go.graphics.event.mouse.GODrawEventProxy; import go.graphics.event.mouse.GOHoverEvent; import go.graphics.event.mouse.GOPanEvent; import go.graphics.event.mouse.GOPanEventProxy; import go.graphics.event.mouse.GOZoomEvent; import go.graphics.region.PositionedRegion; import go.graphics.region.Region; import go.graphics.region.RegionContent; /** * This class represents an area. This is a rectangular part of the screen that consists of multiple regions. * * @author michael */ public class Area implements RedrawListener, GOEventHandlerProvider { /** * How wide is a border of this area? */ public static final int BORDER_SIZE = 1; private int width; private int height; private final ArrayList<Region> regions = new ArrayList<Region>(); private final LinkedList<RedrawListener> redrawListeners = new LinkedList<RedrawListener>(); private ArrayList<PositionedRegion> regionPositions; private Region activeRegion = null; /** * Creates a new area. */ public Area() { } /** * Sets the width of the area. * * @param width * The width in pixels. */ public void setWidth(int width) { this.regionPositions = null; this.width = width; } /** * gets the width of the area. * * @return The width */ public int getWidth() { return width; } public void setHeight(int height) { this.regionPositions = null; this.height = height; } public int getHeight() { return height; } public void add(Region region) { regions.add(region); regionPositions = null; region.addRedrawListener(this); } /** * Draws the area at the given gl context. * <p> * it assumes that the transformation is set so that the lower left corner is (0,0). * * @param gl2 */ public void drawArea(GLDrawContext gl2) { if (regionPositions == null) { recalculateRegions(); } for (int i = 0; i < regionPositions.size(); i++) { PositionedRegion position = regionPositions.get(i); drawRegionAt(gl2, position); // Rectangle border = position.getBorder(); // if (border != null) { // drawBorder(gl2, border); // } } } private static void drawRegionAt(GLDrawContext gl2, PositionedRegion position) { gl2.glPushMatrix(); gl2.glTranslatef(position.getLeft(), position.getBottom(), 0); position.getRegion().drawRegion(gl2, position.getRight() - position.getLeft(), position.getTop() - position.getBottom()); gl2.glPopMatrix(); } private void recalculateRegions() { int regionCount = regions.size(); regionPositions = new ArrayList<PositionedRegion>(); int top = height; int bottom = 0; int left = 0; int right = width; for (int i = 0; i < regionCount; i++) { Region r = regions.get(i); PositionedRegion position = null; if (r.isCollapsed() || bottom > top || left > right) { position = null; continue; } else { int size = r.getSize() + BORDER_SIZE; switch (r.getPosition()) { case Region.POSITION_TOP: if (top - bottom < size) { size = top - bottom; } position = new PositionedRegion(r, top, top - size, left, right); top -= size; break; case Region.POSITION_BOTTOM: if (top - bottom < size) { size = top - bottom; } position = new PositionedRegion(r, size, 0, left, right); bottom += size; break; case Region.POSITION_LEFT: if (right - left < size) { size = right - left; } position = new PositionedRegion(r, top, bottom, 0, size); left += size; break; case Region.POSITION_RIGHT: if (right - left < size) { size = right - left; } position = new PositionedRegion(r, top, bottom, right - size, right); left += size; break; case Region.POSITION_CENTER: default: position = new PositionedRegion(r, top, bottom, left, right); top = bottom; // break; break; } } regionPositions.add(position); } } // /** // * Draws a border for a region. // * // * @param gl2 // * The context to draw on. // * @param position // * The position oft the region to draw the border for. // */ // private void drawBorder(GLDrawContext gl2, Rectangle position) { // gl2.color(.5f, .5f, .5f, 1); // // gl2.fillQuad(position.x, position.y, position.x + position.width, // position.y + position.height); // } /** * Handles a mouse event somewhere in the area and passes it on to the region. * * @param event * The event. */ private void handleMouseEvent(GODrawEvent event) { if (regionPositions == null) { recalculateRegions(); } for (int i = 0; i < regionPositions.size(); i++) { PositionedRegion pos = regionPositions.get(i); if (pos.contentContains(event.getDrawPosition())) { setActiveRegion(regionPositions.get(i).getRegion()); GODrawEventProxy displacedEvent = new GODrawEventProxy(event, new UIPoint(pos.getLeft(), pos.getBottom())); regionPositions.get(i).getRegion().handleEvent(displacedEvent); break; } } } private void handleHoverEvent(GOHoverEvent e) { e.setHandler(new SplitHoverHandler()); } private class SplitHoverHandler implements GOModalEventHandler { private PositionedRegion currentRegion; private RegionContent currentContent; private SimpleHoverEvent sendEvent; @Override public void phaseChanged(GOEvent event) { if (event.getPhase() == GOEvent.PHASE_STARTED) { eventDataChanged(event); } } @Override public void finished(GOEvent event) { if (sendEvent != null) { sendEvent.finish(); } } @Override public void aborted(GOEvent event) { if (event != null) { sendEvent.finish(); } } @Override public void eventDataChanged(GOEvent event) { if (event instanceof GOHoverEvent) { UIPoint point = ((GOHoverEvent) event).getHoverPosition(); PositionedRegion nextRegion = getUnder(point); RegionContent nextContent = nextRegion == null ? null : nextRegion.getRegion().getContent(); if (nextContent != currentContent) { if (currentRegion != null) { endWithRegion(point); } currentRegion = nextRegion; currentContent = nextContent; if (nextRegion != null) { startWithRegion(point); } } else if (sendEvent != null) { changePoint(point); } } } private void startWithRegion(UIPoint areaPoint) { sendEvent = new SimpleHoverEvent(); changePoint(areaPoint); currentRegion.getRegion().handleEvent(sendEvent); sendEvent.initialized(); } private void endWithRegion(UIPoint point) { changePoint(point); sendEvent.finish(); sendEvent = null; } private void changePoint(UIPoint point) { double x = point.getX() - currentRegion.getLeft(); double y = point.getY() - currentRegion.getBottom(); sendEvent.setMousePosition(new UIPoint(x, y)); } } private class SimpleHoverEvent extends AbstractMouseEvent implements GOHoverEvent { public void finish() { this.setPhase(PHASE_FINISHED); } @Override public UIPoint getHoverPosition() { return this.position; } @Override protected void setMousePosition(UIPoint position) { super.setMousePosition(position); } } private PositionedRegion getUnder(UIPoint p) { if (regionPositions == null) { recalculateRegions(); } for (PositionedRegion region : regionPositions) { if (region.contentContains(p)) { return region; } } return null; } public void handleCommandEvent(GOCommandEvent event) { for (int i = 0; i < regionPositions.size(); i++) { PositionedRegion pos = regionPositions.get(i); if (pos.contentContains(event.getCommandPosition())) { GOCommandEventProxy displacedEvent = new GOCommandEventProxy(event, new UIPoint(pos.getLeft(), pos.getBottom())); setActiveRegion(regionPositions.get(i).getRegion()); regionPositions.get(i).getRegion().handleEvent(displacedEvent); break; } } } private void setActiveRegion(Region region) { activeRegion = region; } /** * Handles any known type of event. * * @param event * The event to handle. */ @Override public void handleEvent(GOEvent event) { if (event instanceof GOCommandEvent) { handleCommandEvent((GOCommandEvent) event); } else if (event instanceof GOPanEvent) { handlePanEvent((GOPanEvent) event); } else if (event instanceof GODrawEvent) { handleMouseEvent((GODrawEvent) event); } else if (event instanceof GOHoverEvent) { handleHoverEvent((GOHoverEvent) event); } else if (event instanceof GOKeyEvent) { if (activeRegion != null) { activeRegion.handleEvent(event); } } else if (event instanceof GOZoomEvent) { if (activeRegion == null && regionPositions != null && !regionPositions.isEmpty()) { // if there is no active region, set the first active, // so the zoom is working in the Editor, even if you // didn't press a mouse button setActiveRegion(regionPositions.get(0).getRegion()); } if (activeRegion != null) { activeRegion.handleEvent(event); } } } private void handlePanEvent(GOPanEvent event) { if (event.getPanCenter() == null) { if (!regionPositions.isEmpty()) { PositionedRegion centerPosition = regionPositions .get(regionPositions.size() - 1); centerPosition.getRegion().handleEvent(event); } } else { PositionedRegion foundPosition = getRegionAt(event.getPanCenter()); if (foundPosition != null) { UIPoint topLeft = new UIPoint(foundPosition.getLeft(), foundPosition.getTop()); GOPanEventProxy displacedEvent = new GOPanEventProxy(event, topLeft); foundPosition.getRegion().handleEvent(displacedEvent); } } } private PositionedRegion getRegionAt(UIPoint eventPosition) { Iterator<PositionedRegion> it = regionPositions.iterator(); PositionedRegion foundPosition = null; while (it.hasNext() && foundPosition == null) { PositionedRegion currentPos = it.next(); if (currentPos != null && currentPos.contentContains(eventPosition)) { foundPosition = currentPos; } } return foundPosition; } @Override public void requestRedraw() { for (RedrawListener l : redrawListeners) { l.requestRedraw(); } } public void addRedrawListener(RedrawListener l) { redrawListeners.add(l); } }