diff --git a/src/java/com/threerings/media/ActiveRepaintManager.java b/src/java/com/threerings/media/ActiveRepaintManager.java index fc812378..c2ffc09c 100644 --- a/src/java/com/threerings/media/ActiveRepaintManager.java +++ b/src/java/com/threerings/media/ActiveRepaintManager.java @@ -47,18 +47,17 @@ import com.samskivert.util.RunAnywhere; import com.samskivert.util.StringUtil; /** - * Used to get Swing's repainting to jive with our active rendering - * strategy. + * Used to get Swing's repainting to jive with our active rendering strategy. * * @see FrameManager */ public class ActiveRepaintManager extends RepaintManager { /** - * Components that are rooted in this component (which must be a {@link - * Window} or an {@link Applet}) will be rendered into the offscreen buffer - * managed by the frame manager. Other components will be rendered into - * separate offscreen buffers and repainted in the normal Swing manner. + * Components that are rooted in this component (which must be a {@link Window} or an {@link + * Applet}) will be rendered into the offscreen buffer managed by the frame manager. Other + * components will be rendered into separate offscreen buffers and repainted in the normal + * Swing manner. */ public ActiveRepaintManager (Component root) { @@ -75,9 +74,8 @@ public class ActiveRepaintManager extends RepaintManager // locate the validation root for this component for (Component c = comp; c != null; c = c.getParent()) { - // if the component is not part of an active widget hierarcy, - // we can stop now; if the component is a cell render pane, - // we're apparently supposed to ignore it as wel + // if the component is not part of an active widget hierarcy, we can stop now; if the + // component is a cell render pane, we're apparently supposed to ignore it as wel if (!c.isDisplayable() || c instanceof CellRendererPane) { return; } @@ -87,26 +85,23 @@ public class ActiveRepaintManager extends RepaintManager continue; } - // if we find our validate root, we can stop looking; NOTE: - // JTextField incorrectly claims to be a validate root thereby - // fucking up the program something serious; we jovially - // ignore it's claims here and restore order to the universe; - // see bug #403550 for more fallout from Sun's fuckup - if (!(c instanceof JTextField) && - !(c instanceof JScrollPane) && + // if we find our validate root, we can stop looking; NOTE: JTextField incorrectly + // claims to be a validate root thereby fucking up the program something serious; we + // jovially ignore it's claims here and restore order to the universe; see bug #403550 + // for more fallout from Sun's fuckup + if (!(c instanceof JTextField) && !(c instanceof JScrollPane) && ((JComponent)c).isValidateRoot()) { vroot = c; break; } } - // if we found no validation root we can abort as this component - // is not part of any valid widget hierarchy + // if we found no validation root we can abort as this component is not part of any valid + // widget hierarchy if (vroot == null) { if (DEBUG) { Log.info("Skipping vrootless component: " + toString(comp)); } - return; } @@ -114,34 +109,29 @@ public class ActiveRepaintManager extends RepaintManager // that is showing if (getRoot(vroot) == null) { if (DEBUG) { - Log.info("Skipping rootless component " + - "[comp=" + toString(comp) + + Log.info("Skipping rootless component [comp=" + toString(comp) + ", vroot=" + toString(vroot) + "]."); } return; } - // add the invalid component to our list and we'll validate it on - // the next frame + // add the invalid component to our list and we'll validate it on the next frame if (!ListUtil.containsRef(_invalid, vroot)) { if (DEBUG) { Log.info("Invalidating " + toString(vroot) + "."); } _invalid = ListUtil.add(_invalid, vroot); - // on the mac, components frequently do not repaint themselves - // after being invalidated so we have to force a repaint from - // the validation roon on down + // on the mac, components frequently do not repaint themselves after being invalidated + // so we have to force a repaint from the validation roon on down if (RunAnywhere.isMacOS() && vroot instanceof JComponent) { - addDirtyRegion((JComponent)vroot, 0, 0, - vroot.getWidth(), vroot.getHeight()); + addDirtyRegion((JComponent)vroot, 0, 0, vroot.getWidth(), vroot.getHeight()); } } } // documentation inherited - public synchronized void addDirtyRegion ( - JComponent comp, int x, int y, int width, int height) + public synchronized void addDirtyRegion (JComponent comp, int x, int y, int width, int height) { // ignore invalid requests if ((width <= 0) || (height <= 0) || (comp == null) || @@ -151,8 +141,7 @@ public class ActiveRepaintManager extends RepaintManager return; } - // if this component is already dirty, simply expand their - // existing dirty rectangle + // if this component is already dirty, simply expand their existing dirty rectangle Rectangle drect = (Rectangle)_dirty.get(comp); if (drect != null) { drect.add(x, y); @@ -168,28 +157,25 @@ public class ActiveRepaintManager extends RepaintManager if (DEBUG) { Log.info("Dirtying component [comp=" + toString(comp) + - ", drect=" + StringUtil.toString( - new Rectangle(x, y, width, height)) + "]."); + ", drect=" + StringUtil.toString(new Rectangle(x, y, width, height)) + "]."); } - // if we made it this far, we can queue up a dirty region for this - // component to be repainted on the next tick + // if we made it this far, we can queue up a dirty region for this component to be + // repainted on the next tick _dirty.put(comp, new Rectangle(x, y, width, height)); } /** - * Returns the root component for the supplied component or null if it - * is not part of a rooted hierarchy or if any parent along the way is - * found to be hidden or without a peer. + * Returns the root component for the supplied component or null if it is not part of a rooted + * hierarchy or if any parent along the way is found to be hidden or without a peer. */ protected Component getRoot (Component comp) { for (Component c = comp; c != null; c = c.getParent()) { boolean hidden = !c.isDisplayable(); - // on the mac, the JRootPane is invalidated before it is - // visible and is never again invalidated or repainted, so we - // punt and allow all invisible components to be invalidated - // and revalidated + // on the mac, the JRootPane is invalidated before it is visible and is never again + // invalidated or repainted, so we punt and allow all invisible components to be + // invalidated and revalidated if (!RunAnywhere.isMacOS()) { hidden |= !c.isVisible(); } @@ -207,10 +193,9 @@ public class ActiveRepaintManager extends RepaintManager public synchronized Rectangle getDirtyRegion (JComponent comp) { Rectangle drect = (Rectangle)_dirty.get(comp); - // copy the rectangle if we found one, otherwise create an empty - // rectangle because we don't want them leaving empty handed - return (drect == null) ? - new Rectangle(0, 0, 0, 0) : new Rectangle(drect); + // copy the rectangle if we found one, otherwise create an empty rectangle because we don't + // want them leaving empty handed + return (drect == null) ? new Rectangle(0, 0, 0, 0) : new Rectangle(drect); } // documentation inherited @@ -220,8 +205,7 @@ public class ActiveRepaintManager extends RepaintManager } /** - * Validates the invalid components that have been queued up since the - * last frame tick. + * Validates the invalid components that have been queued up since the last frame tick. */ public void validateComponents () { @@ -268,11 +252,10 @@ public class ActiveRepaintManager extends RepaintManager _dirty = tmap; } - // scan through the list, looking for components for whom a parent - // component is also dirty. in such a case, the dirty rectangle - // for the parent component is expanded to contain the dirty - // rectangle of the child and the child is removed from the - // repaint list (painting the parent will repaint the child) + // scan through the list, looking for components for whom a parent component is also dirty. + // in such a case, the dirty rectangle for the parent component is expanded to contain the + // dirty rectangle of the child and the child is removed from the repaint list (painting + // the parent will repaint the child) Iterator iter = _spare.entrySet().iterator(); PRUNE: while (iter.hasNext()) { @@ -281,31 +264,26 @@ public class ActiveRepaintManager extends RepaintManager Rectangle drect = (Rectangle)entry.getValue(); int x = comp.getX() + drect.x, y = comp.getY() + drect.y; - // climb up the parent hierarchy, looking for the first opaque - // parent as well as the root component + // climb up the parent hierarchy, looking for the first opaque parent as well as the + // root component for (Component c = comp.getParent(); c != null; c = c.getParent()) { - // stop looking for combinable parents for non-visible or - // non-JComponents - if (!c.isVisible() || !c.isDisplayable() || - !(c instanceof JComponent)) { + // stop looking for combinable parents for non-visible or non-JComponents + if (!c.isVisible() || !c.isDisplayable() || !(c instanceof JComponent)) { break; } // check to see if this parent is dirty Rectangle prect = (Rectangle)_spare.get(c); if (prect != null) { - // that we were going to merge it with its parent and - // blow it away + // that we were going to merge it with its parent and blow it away drect.x = x; drect.y = y; if (DEBUG) { - Log.info("Found dirty parent " + - "[comp=" + toString(comp) + + Log.info("Found dirty parent [comp=" + toString(comp) + ", drect=" + StringUtil.toString(drect) + - ", pcomp=" + toString(c) + - ", prect=" + StringUtil.toString(prect) + - "]."); + ", pcomp=" + toString(c) + + ", prect=" + StringUtil.toString(prect) + "]."); } prect.add(drect); @@ -325,26 +303,24 @@ public class ActiveRepaintManager extends RepaintManager } } - // now paint each of the dirty components, by setting the clipping - // rectangle appropriately and calling paint() on the associated - // root component + // now paint each of the dirty components, by setting the clipping rectangle appropriately + // and calling paint() on the associated root component iter = _spare.entrySet().iterator(); while (iter.hasNext()) { Entry entry = (Entry)iter.next(); JComponent comp = (JComponent)entry.getKey(); Rectangle drect = (Rectangle)entry.getValue(); - // get the root component, adjust the clipping (dirty) - // rectangle and obtain the bounds of the client in absolute - // coordinates + // get the root component, adjust the clipping (dirty) rectangle and obtain the bounds + // of the client in absolute coordinates Component root = null, ocomp = null; - // start with the components bounds which we'll switch to the - // opaque parent component's bounds if and when we find one + // start with the components bounds which we'll switch to the opaque parent component's + // bounds if and when we find one _cbounds.setBounds(0, 0, comp.getWidth(), comp.getHeight()); - // climb up the parent hierarchy, looking for the first opaque - // parent as well as the root component + // climb up the parent hierarchy, looking for the first opaque parent as well as the + // root component for (Component c = comp; c != null; c = c.getParent()) { if (!c.isVisible() || !c.isDisplayable()) { break; @@ -354,16 +330,14 @@ public class ActiveRepaintManager extends RepaintManager // make a note of the first opaque parent we find if (ocomp == null && ((JComponent)c).isOpaque()) { ocomp = c; - // we need to obtain the opaque parent's coordinates - // in the root coordinate system for when we repaint - _cbounds.setBounds( - 0, 0, ocomp.getWidth(), ocomp.getHeight()); + // we need to obtain the opaque parent's coordinates in the root coordinate + // system for when we repaint + _cbounds.setBounds(0, 0, ocomp.getWidth(), ocomp.getHeight()); } } else { - // oh god the hackery. apparently the fscking - // JEditorPane wraps a heavy weight component around - // every swing component it uses when doing forms + // oh god the hackery. apparently the fscking JEditorPane wraps a heavy weight + // component around every swing component it uses when doing forms Component tp = c.getParent(); if (!(tp instanceof JEditorPane)) { root = c; @@ -371,8 +345,7 @@ public class ActiveRepaintManager extends RepaintManager } } - // translate the coordinates into this component's - // coordinate system + // translate the coordinates into this component's coordinate system drect.x += c.getX(); drect.y += c.getY(); _cbounds.x += c.getX(); @@ -383,19 +356,17 @@ public class ActiveRepaintManager extends RepaintManager c.getX(), c.getY(), c.getWidth(), c.getHeight(), drect); } - // if we found no opaque parent, just paint the component - // itself (this seems to happen with the top-level layered - // pane) + // if we found no opaque parent, just paint the component itself (this seems to happen + // with the top-level layered pane) if (ocomp == null) { ocomp = comp; } - // if this component is rooted in our frame, repaint it into - // the supplied graphics instance + // if this component is rooted in our frame, repaint it into the supplied graphics + // instance if (root == _root) { if (DEBUG) { - Log.info("Repainting [comp=" + toString(comp) + - StringUtil.toString(_cbounds) + + Log.info("Repainting [comp=" + toString(comp) + StringUtil.toString(_cbounds) + ", ocomp=" + toString(ocomp) + ", drect=" + StringUtil.toString(drect) + "]."); } @@ -403,36 +374,30 @@ public class ActiveRepaintManager extends RepaintManager g.setClip(drect); g.translate(_cbounds.x, _cbounds.y); try { - // some components are ill-behaved and may throw an - // exception while painting themselves, and so we - // needs must deal with these fellows gracefully + // some components are ill-behaved and may throw an exception while painting + // themselves, and so we needs must deal with these fellows gracefully ocomp.paint(g); } catch (Exception e) { - Log.warning("Exception while painting component " + - "[comp=" + ocomp + ", e=" + e + "]."); + Log.warning("Exception while painting component [comp=" + ocomp + "]."); Log.logStackTrace(e); } g.translate(-_cbounds.x, -_cbounds.y); - // we also need to repaint any components in this layer - // that are above our freshly repainted component + // we also need to repaint any components in this layer that are above our freshly + // repainted component fmgr.renderLayers((Graphics2D)g, ocomp, _cbounds, _clipped); } else if (root != null) { if (DEBUG) { - Log.info("Repainting old-school " + - "[comp=" + toString(comp) + - ", ocomp=" + toString(ocomp) + - ", root=" + toString(root) + - ", bounds=" + StringUtil.toString(_cbounds) + - "]."); + Log.info("Repainting old-school [comp=" + toString(comp) + + ", ocomp=" + toString(ocomp) + ", root=" + toString(root) + + ", bounds=" + StringUtil.toString(_cbounds) + "]."); dumpHierarchy(comp); } // otherwise, repaint with standard swing double buffers - Image obuf = getOffscreenBuffer( - ocomp, _cbounds.width, _cbounds.height); + Image obuf = getOffscreenBuffer(ocomp, _cbounds.width, _cbounds.height); Graphics og = null, cg = null; try { og = obuf.getGraphics(); @@ -462,8 +427,7 @@ public class ActiveRepaintManager extends RepaintManager */ protected static String toString (Component comp) { - return comp.getClass().getName() + - StringUtil.toString(comp.getBounds()); + return comp.getClass().getName() + StringUtil.toString(comp.getBounds()); } /** @@ -486,8 +450,7 @@ public class ActiveRepaintManager extends RepaintManager /** A mapping of invalid rectangles for each widget that is dirty. */ protected HashMap _dirty = new HashMap(); - /** A spare hashmap that we swap in while repainting dirty components - * in the old hashmap. */ + /** A spare hashmap that we swap in while repainting dirty components in the old hashmap. */ protected HashMap _spare = new HashMap(); /** Used to compute dirty components' bounds. */ @@ -496,7 +459,6 @@ public class ActiveRepaintManager extends RepaintManager /** Used when rendering "layered" components. */ protected boolean[] _clipped = new boolean[] { true }; - /** We debug so much that we have to make it easy to enable and - * disable debug logging. Yay! */ + /** We debug so much that we have to make it easy to enable and disable debug logging. Yay! */ protected static final boolean DEBUG = false; } diff --git a/src/java/com/threerings/media/FrameManager.java b/src/java/com/threerings/media/FrameManager.java index 00dc65b2..ff1e2187 100644 --- a/src/java/com/threerings/media/FrameManager.java +++ b/src/java/com/threerings/media/FrameManager.java @@ -221,7 +221,6 @@ public abstract class FrameManager { if (_overlay == null) { _overlay = new MediaOverlay(this); - registerFrameParticipant(_overlay); } return _overlay; } @@ -232,7 +231,6 @@ public abstract class FrameManager public void clearMediaOverlay () { if (_overlay != null) { - removeFrameParticipant(_overlay); _overlay = null; } } @@ -330,11 +328,11 @@ public abstract class FrameManager _timer = timer; // set up our custom repaint manager - _remgr = new ActiveRepaintManager(_window); - RepaintManager.setCurrentManager(_remgr); + _repainter = new ActiveRepaintManager(_window); + RepaintManager.setCurrentManager(_repainter); // turn off double buffering for the whole business because we handle repaints - _remgr.setDoubleBufferingEnabled(false); + _repainter.setDoubleBufferingEnabled(false); } /** @@ -377,7 +375,7 @@ public abstract class FrameManager // validate any invalid components try { - _remgr.validateComponents(); + _repainter.validateComponents(); } catch (Throwable t) { Log.warning("Failure validating components."); Log.logStackTrace(t); @@ -412,6 +410,11 @@ public abstract class FrameManager Log.logStackTrace(t); } } + + // if we have an overlay, tick that too + if (_overlay != null) { + _overlay.tick(tickStamp); + } } /** @@ -494,8 +497,19 @@ public abstract class FrameManager } } + // if we have a media overlay, give that a chance to propagate dirty regions to the active + // repaint manager for areas it dirtied during this tick + if (_overlay != null) { + _overlay.propagateDirtyRegions(_repainter, _root.getRootPane()); + } + // repaint any widgets that have declared they need to be repainted since the last tick - boolean pcomp = _remgr.paintComponents(gfx, this); + boolean pcomp = _repainter.paintComponents(gfx, this); + + // if we have a media overlay, give it a chance to paint on top of everything + if (_overlay != null) { + pcomp |= _overlay.paint(gfx); + } // let the caller know if anybody painted anything return ((painted > 0) || pcomp); @@ -676,7 +690,7 @@ public abstract class FrameManager protected MediaTimer _timer; /** Our custom repaint manager. */ - protected ActiveRepaintManager _remgr; + protected ActiveRepaintManager _repainter; /** If active, an overlay that will be rendering sprites and animations on top of the frame. */ protected MediaOverlay _overlay; diff --git a/src/java/com/threerings/media/MediaOverlay.java b/src/java/com/threerings/media/MediaOverlay.java index 87c279c8..87e0075f 100644 --- a/src/java/com/threerings/media/MediaOverlay.java +++ b/src/java/com/threerings/media/MediaOverlay.java @@ -23,8 +23,9 @@ package com.threerings.media; import java.awt.Component; import java.awt.Graphics2D; +import java.awt.Rectangle; +import javax.swing.JRootPane; -import com.threerings.media.FrameParticipant; import com.threerings.media.animation.AnimationManager; import com.threerings.media.sprite.SpriteManager; @@ -35,7 +36,7 @@ import com.threerings.media.sprite.SpriteManager; * it has left dirty. */ public class MediaOverlay - implements MediaHost, FrameParticipant + implements MediaHost { /** * Returns a reference to the animation manager used by this media panel. @@ -53,13 +54,51 @@ public class MediaOverlay return _metamgr.getSpriteManager(); } + /** + * Called by the {@link FrameManager} to propagate our dirty regions to the active repaint + * manager so that it can repaint the underlying components just prior to our painting our + * media. This will be followe by a call to {@link paint} after the components have been + * repainted. + */ + public void propagateDirtyRegions (ActiveRepaintManager repmgr, JRootPane root) + { + // this will clear out our region manager, so we need to keep these around for our + // subsequent call to paint + _dirty = _metamgr.getRegionManager().getDirtyRegions(); + for (int ii = 0; ii < _dirty.length; ii++) { + Rectangle dirty = _dirty[ii]; + repmgr.addDirtyRegion(root, dirty.x, dirty.y, dirty.width, dirty.height); + } + } + + /** + * Called by the {@link FrameManager} after everything is done painting, allowing us to paint + * gloriously overtop of everything in the frame. + * + * @return true if we painted something, false otherwise. + */ + public boolean paint (Graphics2D gfx) + { + if (_metamgr.needsPaint()) { + for (int ii = 0; ii < _dirty.length; ii++) { + gfx.setClip(_dirty[ii]); + _metamgr.paintMedia(gfx, MediaConstants.BACK, _dirty[ii]); + _metamgr.paintMedia(gfx, MediaConstants.FRONT, _dirty[ii]); + } + return true; + } + return false; + } + // from interface MediaHost public Graphics2D createGraphics () { return _metamgr.getFrameManager().createGraphics(); } - // from interface FrameParticipant + /** + * Called by the frame manager on every tick. + */ public void tick (long tickStamp) { if (!_metamgr.isPaused()) { @@ -68,18 +107,6 @@ public class MediaOverlay } } - // from interface FrameParticipant - public boolean needsPaint () - { - return _metamgr.needsPaint(); - } - - // from interface FrameParticipant - public Component getComponent () - { - return null; // TODO - } - /** * Creates a media overlay. Only the {@link FrameManager} will construct an instance. */ @@ -94,4 +121,7 @@ public class MediaOverlay /** Handles the heavy lifting involving media. */ protected MetaMediaManager _metamgr; + + /** A temporary list of dirty rectangles maintained during the painting process. */ + protected Rectangle[] _dirty; } diff --git a/src/java/com/threerings/media/MediaPanel.java b/src/java/com/threerings/media/MediaPanel.java index 307920c8..bda74fbe 100644 --- a/src/java/com/threerings/media/MediaPanel.java +++ b/src/java/com/threerings/media/MediaPanel.java @@ -127,6 +127,14 @@ public class MediaPanel extends JComponent return _metamgr.getSpriteManager(); } + /** + * Returns a reference to the region manager used by this media panel. + */ + public RegionManager getRegionManager () + { + return _metamgr.getRegionManager(); + } + /** * Pauses the sprites and animations that are currently active on this media panel. Also stops * listening to the frame tick while paused. diff --git a/src/java/com/threerings/media/RegionManager.java b/src/java/com/threerings/media/RegionManager.java index 72df0423..5a06f643 100644 --- a/src/java/com/threerings/media/RegionManager.java +++ b/src/java/com/threerings/media/RegionManager.java @@ -29,9 +29,8 @@ import java.util.ArrayList; import com.samskivert.util.StringUtil; /** - * Manages regions (rectangles) that are invalidated in the process of - * ticking animations and sprites and generally doing other display - * related business. + * Manages regions (rectangles) that are invalidated in the process of ticking animations and + * sprites and generally doing other display related business. */ public class RegionManager { @@ -46,9 +45,8 @@ public class RegionManager } /** - * Invalidates the specified region (the supplied rectangle will be - * cloned as the region manager fiddles with the rectangles it uses - * internally). + * Invalidates the specified region (the supplied rectangle will be cloned as the region + * manager fiddles with the rectangles it uses internally). */ public void invalidateRegion (Rectangle rect) { @@ -58,17 +56,15 @@ public class RegionManager } /** - * Adds the supplied rectangle to the dirty regions. Control of the - * rectangle is given to the region manager as it may choose to bend, - * fold or mutilate it later. If you don't want the region manager - * messing with your rectangle, use {@link #invalidateRegion}. + * Adds the supplied rectangle to the dirty regions. Control of the rectangle is given to the + * region manager as it may choose to bend, fold or mutilate it later. If you don't want the + * region manager messing with your rectangle, use {@link #invalidateRegion}. */ public void addDirtyRegion (Rectangle rect) { // make sure we're on an AWT thread if (!EventQueue.isDispatchThread()) { - Log.warning("Oi! Region dirtied on non-AWT thread " + - "[rect=" + rect + "]."); + Log.warning("Oi! Region dirtied on non-AWT thread [rect=" + rect + "]."); Thread.dumpStack(); } @@ -81,8 +77,7 @@ public class RegionManager // more sanity checking long x = rect.x, y = rect.y; - if ((Math.abs(x) > Integer.MAX_VALUE/2) || - (Math.abs(y) > Integer.MAX_VALUE/2)) { + if ((Math.abs(x) > Integer.MAX_VALUE/2) || (Math.abs(y) > Integer.MAX_VALUE/2)) { Log.warning("Requested to dirty questionable region " + "[rect=" + StringUtil.toString(rect) + "]."); if (Log.getLevel() == Log.log.DEBUG) { @@ -107,8 +102,7 @@ public class RegionManager return false; } else if (width == 0 || height == 0) { - // no need to complain about zero sized rectangles, just - // ignore them + // no need to complain about zero sized rectangles, just ignore them return false; } else { @@ -117,8 +111,8 @@ public class RegionManager } /** - * Returns true if dirty regions have been accumulated since the last - * call to {@link #getDirtyRegions}. + * Returns true if dirty regions have been accumulated since the last call to {@link + * #getDirtyRegions}. */ public boolean haveDirtyRegions () { @@ -126,10 +120,9 @@ public class RegionManager } /** - * Merges all outstanding dirty regions into a single list of - * rectangles and returns that to the caller. Interally, the list of - * accumulated dirty regions is cleared out and prepared for the next - * frame. + * Merges all outstanding dirty regions into a single list of rectangles and returns that to + * the caller. Interally, the list of accumulated dirty regions is cleared out and prepared for + * the next frame. */ public Rectangle[] getDirtyRegions () {