/******************************************************************************* * Copyright (c) 2007, 2008 Gregory Jordan * * This file is part of PhyloWidget. * * PhyloWidget 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 2 of the License, or (at your option) any later * version. * * PhyloWidget 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 * PhyloWidget. If not, see <http://www.gnu.org/licenses/>. */ package org.andrewberman.ui.menu; import java.awt.BasicStroke; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.Stroke; import java.awt.event.FocusEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.geom.RoundRectangle2D; import org.andrewberman.ui.Color; import org.andrewberman.ui.Point; import org.andrewberman.ui.UIUtils; import processing.core.PApplet; import processing.core.PFont; import processing.core.PGraphics; /** * The <code>Dock</code> class is a close approximation of Apple's infamous * Dock menubar. * <p> * It is built on top of the <code>Menu</code> superclass, except unlike most * other <code>Menu</code> derivatives, the <code>Dock</code> does not rely * on Java2D rendering (although it makes use of it if available). As such, it * can be drawn directly to the canvas, without requiring the processor and * memory overhead of creating and drawing to an off-screen buffer. Thus, it * should be snappy under P3D and OpenGL renderers. * <p> * By default, the <code>Dock</code> "docks" and centers itself along one side * of the screen. You can alter this behavior by * * @author Greg */ public class Dock extends Menu { public static final int LEFT = 0; public static final int RIGHT = 1; public static final int TOP = 2; public static final int BOTTOM = 3; DockRotationHandler rotation; RoundRectangle2D.Float drawRect = new RoundRectangle2D.Float(0, 0, 0, 0, 0, 0); RoundRectangle2D.Float mouseRect = new RoundRectangle2D.Float(0, 0, 0, 0, 0, 0); Point mousePt = new Point(0, 0); float origWidth, inset, offset, maxPossibleWidth; float curWidth, curHeight, curLow; boolean isActivated; /** * The amount by which the icons "bulge" when approached. */ public float bulgeAmount = .7f; /** * The "rolloff" factor for the icons' bulge. Play around with it to find a * value that you like. */ public float bulgeWidth = 20.0f; /** * If set to true, then this Dock will automatically center itself to the * side against which it's docked. Offset will offset it in the positive x * or y direction away from the center. If false, then the offset will cause * the dock to be offset from the corner by that amount. * <p> * Note that in the <code>false</code> case, the offset calculated will be * based on the <em>resting</em> size of the <code>Dock</code> When * items become bulged, the dock will likely extend a bit out in both * directions, so you'd best give it a little extra <code>offset</code> * just to be safe! */ public boolean autoCenter; /** * If set to true, then a triangle will be drawn on the last clicked item. * If false, no triangles. Simple! */ public boolean triangleOnSelected; public boolean consumeWhenActive; public Dock(PApplet app) { super(app); inset = getStyle().getF("f.padX"); rotation = new DockRotationHandler(); rotation.setRotation(BOTTOM); setWidth(40); open(); } public void setOptions() { super.setOptions(); useCameraCoordinates = false; clickAwayBehavior = CLICKAWAY_COLLAPSES; hoverNavigable = false; clickToggles = true; useHandCursor = true; actionOnMouseDown = true; usesJava2D = true; autoDim = true; autoCenter = true; triangleOnSelected = true; consumeWhenActive = true; } public synchronized void layout() { if (!isOpen()) return; mousePt.setLocation(canvas.mouseX, canvas.mouseY); if (useCameraCoordinates) UIUtils.screenToModel(mousePt); float mousePos = mousePos(); float origHeight = origWidth * items.size(); float origCenter = rotation.getCenter(); float origLow = origCenter - origHeight / 2; float mouseOffset = mousePos - origLow; float newOffset = 0; float pos = origLow; for (MenuItem item : items) { float mid = pos + origWidth / 2; float scale = bulge(mid - mousePos); item.setSize(origWidth * scale, origWidth * scale); newOffset += item.getHeight(); pos += origWidth; } curLow = origLow - (newOffset - origHeight) * (mouseOffset / origHeight); pos = curLow; float maxWidth = 0; for (int i = 0; i < items.size(); i++) { MenuItem item = (MenuItem) items.get(i); rotation.positionItem(item, pos); pos += item.getHeight(); if (item.getWidth() > maxWidth) maxWidth = item.getWidth(); } curHeight = pos - curLow; curWidth = maxWidth; } float mousePos() { return rotation.getMousePos(mousePt); } float bulge(float dist) { if (isActivated) { return 1.0f + bulgeAmount * PApplet.exp(-dist * dist / (bulgeWidth * bulgeWidth)); } else return 1.0f; } public void setOffsetFromCenter(float offset) { this.offset = offset; layout(); } public void setWidth(float newWidth) { origWidth = newWidth; maxPossibleWidth = origWidth * (1 + bulgeAmount); layout(); } public void setInset(float inset) { this.inset = inset; layout(); } public void setRotation(String rot) { if (rot.equalsIgnoreCase("left")) rotation.setRotation(LEFT); else if (rot.equalsIgnoreCase("right")) rotation.setRotation(RIGHT); if (rot.equalsIgnoreCase("top")) rotation.setRotation(TOP); if (rot.equalsIgnoreCase("bottom")) rotation.setRotation(BOTTOM); } public DockItem getSelectedItem() { if (lastPressed == null) return null; else return (DockItem) lastPressed; } void resetPosition() { for (MenuItem item : items) { DockItem d = (DockItem) item; d.forceSize(origWidth, origWidth); } layout(); } public synchronized void drawMyself() { layout(); rotation.setRect(drawRect, origWidth); Color strokeC = getStyle().getC("c.foreground"); float sw = getStyle().getF("f.strokeWeight"); Stroke stroke = new BasicStroke(sw); float px = getStyle().getF("f.padX"); float py = getStyle().getF("f.padY"); PFont font = getStyle().getFont("font"); /* * Draw a nice-looking background gradient. */ if (usesJava2D) { Graphics2D g2 = menu.buff.g2; // Graphics2D g2 = ((PGraphicsJava2D) menu.canvas.g).g2; g2.setPaint(getStyle().getGradient(MenuItem.UP, 0, (float) drawRect .getMinY(), 0, (float) drawRect.getMaxY())); g2.fill(drawRect); g2.setStroke(stroke); g2.setPaint(strokeC); RenderingHints rh = g2.getRenderingHints(); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.draw(drawRect); g2.setRenderingHints(rh); } else { int alpha = (int) (menu.alpha * 255); int color; Color c; canvas.beginShape(PApplet.QUADS); canvas.stroke(canvas.color(strokeC.getRGB(), alpha)); c = getStyle().getC("c.gradientLo"); color = canvas.color(c.getRed(), c.getGreen(), c.getBlue(), alpha); canvas.fill(color); canvas.vertex((float) drawRect.getMinX(), (float) drawRect .getMinY()); canvas.vertex((float) drawRect.getMaxX(), (float) drawRect .getMinY()); c = getStyle().getC("c.gradientHi"); color = canvas.color(c.getRed(), c.getGreen(), c.getBlue(), alpha); canvas.fill(color); canvas.vertex((float) drawRect.getMaxX(), (float) drawRect .getMaxY()); canvas.vertex((float) drawRect.getMinX(), (float) drawRect .getMaxY()); canvas.endShape(); } if (hovered != null) { DockItem i = (DockItem) hovered; float fontSize = origWidth / 2f; float ascent = UIUtils.getTextAscent(menu.canvas.g, font, fontSize, false); float descent = UIUtils.getTextDescent(menu.canvas.g, font, fontSize, false); float tHeight = (ascent + descent); float tWidth = UIUtils.getTextWidth(menu.canvas.g, font, fontSize, i.getLabel(), false); float tX = 0; float tY = 0; switch (rotation.rot) { case (LEFT): menu.canvas.textAlign(PApplet.LEFT); tX = inset + maxPossibleWidth + px; tY = i.getY() + i.getHeight() / 2 + tHeight / 2 - descent / 2; break; case (RIGHT): menu.canvas.textAlign(PApplet.LEFT); tX = menu.canvas.width - inset - maxPossibleWidth - px - tWidth; tY = i.getY() + i.getHeight() / 2 + tHeight / 2 - descent / 2; break; case (TOP): menu.canvas.textAlign(PApplet.CENTER); tX = i.getX() + i.getWidth() / 2; tY = inset + maxPossibleWidth + px + tHeight - descent; break; case (BOTTOM): menu.canvas.textAlign(PApplet.CENTER); tX = i.getX() + i.getWidth() / 2; tY = menu.canvas.width - inset - maxPossibleWidth - px; break; } if (usesJava2D) { MenuUtils.drawWhiteTextRect(this, tX - px, tY - ascent - px, tWidth + px * 2, tHeight + px * 2); } Color c = strokeC; int alpha = (int) (menu.alpha * 255); menu.canvas.fill(menu.canvas.color(c.getRed(), c.getGreen(), c .getBlue(), alpha)); // menu.canvas.fill(0,alpha); menu.canvas.textFont(context.getPFont()); menu.canvas.textSize(fontSize); menu.canvas.text(i.getLabel(), tX, tY); menu.canvas.textAlign(PApplet.LEFT); } if (lastPressed != null && triangleOnSelected) { MenuItem i = lastPressed; PGraphics pg = canvas.g; int alpha = (int) (menu.alpha * 255); Color c = strokeC; pg.fill(menu.canvas.color(c.getRed(), c.getGreen(), c.getBlue(), alpha)); float height = i.getWidth() / 8; switch (rotation.rot) { case (LEFT): float cy = i.getY() + i.getHeight() / 2; float cx = inset + px; pg.triangle(cx, cy + height, cx, cy - height, cx + height, cy); i.setPosition(i.getX() + height, i.getY()); break; case (RIGHT): cy = i.getY() + i.getHeight() / 2; cx = canvas.width - inset - px; pg.triangle(cx, cy + height, cx, cy - height, cx - height, cy); i.setPosition(i.getX() - height, i.getY()); break; case (TOP): cy = inset + py; cx = i.getX() + i.getWidth() / 2; pg.triangle(cx + height, cy, cx - height, cy, cx, cy + height); i.setPosition(i.getX(), i.getY() + height); break; case (BOTTOM): cy = canvas.width - inset - py; cx = i.getX() + i.getWidth() / 2; pg.triangle(cx + height, cy, cx - height, cy, cx, cy - height); i.setPosition(i.getX(), i.getY() - height); break; } } } public void focusEvent(FocusEvent e) { if (e.getID() == FocusEvent.FOCUS_LOST) { isActivated = false; layout(); } } public void keyEvent(KeyEvent e) { super.keyEvent(e); } @Override public void setState(MenuItem i, int s) { super.setState(i, s); } // public void setHidden(String s) // { // if (s.startsWith("t")) // { // close(); // } else // open(); // } public void mouseEvent(MouseEvent e, Point screen, Point model) { if (useCameraCoordinates) mousePt.setLocation(model); else mousePt.setLocation(screen); if (e.getID() == MouseEvent.MOUSE_MOVED || e.getID() == MouseEvent.MOUSE_DRAGGED) { if (containsPoint(mousePt)) { isActivated = true; context.focus().setModalFocus(this); } else { isActivated = false; context.focus().removeFromFocus(this); } } super.mouseEvent(e, screen, model); if (isActivated && consumeWhenActive) { e.consume(); } } public boolean containsPoint(Point pt) { if (mouseRect == null || rotation == null) return false; rotation.setRect(mouseRect, curWidth); return mouseRect.contains(pt); } public float getX() { return 0; } public float getY() { return 0; } public void setPosition(float inset, float offset) { this.inset = inset; this.offset = offset; layout(); } public MenuItem create(String label) { DockItem di = new DockItem(); di.setName(label); return di; } public DockItem add(String label, String iconFile) { DockItem addMe = new DockItem(); addMe.setName(label); addMe.setIcon(iconFile); add(addMe); return addMe; } @Override public MenuItem add(MenuItem item) { super.add(item); resetPosition(); return item; } class DockRotationHandler { int rot = LEFT; void setRotation(int i) { rot = i; layout(); } boolean isHorizontal() { return (rot == TOP || rot == BOTTOM); } boolean isVertical() { return (rot == LEFT || rot == RIGHT); } float getMousePos(Point pt) { switch (rot) { case (TOP): case (BOTTOM): return pt.x; case (LEFT): case (RIGHT): default: return pt.y; } } float getCenter() { if (!autoCenter) return offset + origWidth * items.size() / 2; else { switch (rot) { case (TOP): case (BOTTOM): return canvas.width / 2 + offset; case (LEFT): case (RIGHT): default: return canvas.height / 2 + offset; } } } void setRect(RoundRectangle2D.Float rect, float width) { float r = origWidth / 4; switch (rot) { case (RIGHT): rect.setRoundRect(canvas.width - inset - width, curLow, width, curHeight, r, r); break; case (TOP): rect.setRoundRect(curLow, inset, curHeight, width, r, r); break; case (BOTTOM): rect.setRoundRect(curLow, canvas.height - inset - width, curHeight, width, r, r); break; case (LEFT): default: rect.setRoundRect(inset, curLow, width, curHeight, r, r); } } void positionItem(MenuItem item, float pos) { DockItem d = (DockItem) item; PGraphics pg = canvas.g; switch (rot) { case (LEFT): d.setPosition(inset, pos); break; case (RIGHT): d.setPosition(pg.width - item.getWidth() - inset, pos); break; case (TOP): d.setPosition(pos, inset); break; case (BOTTOM): d.setPosition(pos, pg.height - item.getHeight() - inset); break; } } } public float getBulgeAmount() { return bulgeAmount; } public void setBulgeAmount(float bulgeAmount) { this.bulgeAmount = bulgeAmount; } public float getBulgeWidth() { return bulgeWidth; } public void setBulgeWidth(float bulgeWidth) { this.bulgeWidth = bulgeWidth; } }