/* * Copyright (C) 2009 Quadduc <quadduc@gmail.com> * * Contains all the methods for managing the zoom * * This file is part of LateralGM. * LateralGM is free software and comes with ABSOLUTELY NO WARRANTY. * See LICENSE for details. */ package org.lateralgm.components.visual; import static org.lateralgm.main.Util.negDiv; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.util.TreeMap; import javax.swing.JPanel; import org.lateralgm.ui.swing.visuals.BoundedVisual; import org.lateralgm.ui.swing.visuals.Visual; import org.lateralgm.ui.swing.visuals.VisualContainer; public class VisualPanel extends JPanel { private static final long serialVersionUID = 1L; public static final Point ORIGIN_MOUSE = new Point(); private final TreeMap<Integer,Visual> visuals = new TreeMap<Integer,Visual>(); private final Rectangle overallBounds = new Rectangle(); public final VisualContainer container = new PanelVisualContainer(); private int zoom = 1; protected Point zoomOrigin; private boolean boundsLocked = false; private boolean boundsUpdated = false; public VisualPanel() { setBackground(Color.GRAY); setOpaque(true); } protected void componentToVisual(Point p) { componentToVisual(p,zoom); } protected void componentToVisual(Point p, int z) { p.x = zoom(p.x - visualOffsetX(z),2 - z) + overallBounds.x; p.y = zoom(p.y - visualOffsetY(z),2 - z) + overallBounds.y; } protected void componentToVisual(Rectangle r, int z) { Point p = r.getLocation(); componentToVisual(p,z); r.setLocation(p); r.width = zoom(r.width,2 - z); r.height = zoom(r.height,2 - z); } public void visualToComponent(Point p) { visualToComponent(p,zoom); } public void visualToComponent(Point p, int z) { p.x = zoom(p.x - overallBounds.x,z) + visualOffsetX(z); p.y = zoom(p.y - overallBounds.y,z) + visualOffsetY(z); } protected void visualToComponent(Rectangle r, int z) { Point p = r.getLocation(); visualToComponent(p,z); r.setLocation(p); r.width = zoom(r.width,z); r.height = zoom(r.height,z); } public Rectangle getOverallBounds() { return overallBounds; } protected static double zoom(double d, int z) { return z > 0 ? z * d : d / (2 - z); } protected static int zoom(int i, int z) { return z > 0 ? z * i : negDiv(i,2 - z); } protected static int zoomAlign(int i, int z, boolean ceil) { return z <= 0 ? (2 - z) * negDiv(i + (ceil ? 1 - z : 0),2 - z) : i; } protected static void zoomAlign(Rectangle r, int z, boolean out) { int x0 = zoomAlign(r.x,z,!out); int y0 = zoomAlign(r.y,z,!out); r.setBounds(x0,y0,zoomAlign(r.width + r.x - x0,z,out),zoomAlign(r.height + r.y - y0,z,out)); } protected static void zoom(Rectangle r, int z) { r.x = zoom(r.x,z); r.y = zoom(r.y,z); r.width = zoom(r.width,z); r.height = zoom(r.height,z); } protected int visualOffsetX(int z) { return (getWidth() - zoom(overallBounds.width,z)) / 2; } protected int visualOffsetY(int z) { return (getHeight() - zoom(overallBounds.height,z)) / 2; } protected Rectangle getOverallBounds(Rectangle r) { if (r == null) return new Rectangle(overallBounds); r.setBounds(overallBounds); return r; } protected static void calculateOverallBounds(Rectangle b) { b.grow(128,128); b.add(0,0); } @Override public Dimension getPreferredSize() { Dimension s = overallBounds.getSize(); s.width = zoom(s.width,zoom); s.height = zoom(s.height,zoom); return s; } @Override public Dimension getMinimumSize() { return getPreferredSize(); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); Graphics g2 = g.create(); if (g2.getClip() == null) g2.setClip(0,0,getWidth(),getHeight()); g2.translate(visualOffsetX(zoom),visualOffsetY(zoom)); if (zoom != 1) { double s = zoom(1.0,zoom); Graphics2D g3 = ((Graphics2D) g2); g3.scale(s,s); g3.setRenderingHint(RenderingHints.KEY_INTERPOLATION, zoom < 1 ? RenderingHints.VALUE_INTERPOLATION_BILINEAR : RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); } g2.translate(-overallBounds.x,-overallBounds.y); paintVisuals(g2); g2.dispose(); } protected void paintVisuals(Graphics g) { for (Visual v : visuals.values()) v.paint(g); } public void put(int layer, Visual v) { Visual pv = v == null ? visuals.remove(layer) : visuals.put(layer,v); if (pv == v) return; if (v instanceof BoundedVisual || pv instanceof BoundedVisual) container.updateBounds(); repaint(); } protected void lockBounds() { boundsLocked = true; } protected void unlockBounds() { boundsLocked = false; if (boundsUpdated) { container.updateBounds(); boundsUpdated = false; } } private class PanelVisualContainer implements VisualContainer { private int oldZoom = zoom; public void repaint(Rectangle r) { if (r == null) VisualPanel.this.repaint(); else { Rectangle cr = r.getBounds(); zoomAlign(cr,zoom,true); visualToComponent(cr,zoom); VisualPanel.this.repaint(cr); } } public void updateBounds() { boolean uob = (!boundsLocked); if (boundsLocked) boundsUpdated = true; boolean uz = zoom != oldZoom; if (!uob && !uz) return; Point o = zoomOrigin; Point co; if (o == null || o == ORIGIN_MOUSE) { co = o == null ? null : getMousePosition(); if (co == null) { Rectangle vr = getVisibleRect(); co = new Point(vr.x + vr.width / 2,vr.y + vr.height / 2); } o = co.getLocation(); componentToVisual(o,oldZoom); } else { co = o.getLocation(); visualToComponent(co,oldZoom); } Rectangle oob = overallBounds.getBounds(); if (uob) { overallBounds.setSize(-1,-1); for (Visual v : visuals.values()) if (v instanceof BoundedVisual) ((BoundedVisual) v).extendBounds(overallBounds); calculateOverallBounds(overallBounds); } zoomAlign(overallBounds,zoom,true); if (oob.equals(overallBounds) && !uz) return; Point p = getLocation(); setBounds(p.x + co.x - zoom(o.x - overallBounds.x,zoom), p.y + co.y - zoom(o.y - overallBounds.y,zoom), Math.max(getWidth(),zoom(overallBounds.width,zoom)), Math.max(getHeight(),zoom(overallBounds.height,zoom))); oldZoom = zoom; revalidate(); } } public void setZoom(int z) { if (zoom == z) return; zoom = z; container.updateBounds(); } }