
com.threerings.media.ActiveRepaintManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of nenya Show documentation
Show all versions of nenya Show documentation
Facilities for making networked multiplayer games.
The newest version!
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// https://github.com/threerings/nenya
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media;
import java.applet.Applet;
import java.util.Iterator;
import java.util.Map;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Window;
import javax.swing.CellRendererPane;
import javax.swing.JComponent;
import javax.swing.JEditorPane;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.RepaintManager;
import javax.swing.SwingUtilities;
import com.google.common.collect.Maps;
import com.samskivert.util.ListUtil;
import com.samskivert.util.RunAnywhere;
import com.samskivert.util.StringUtil;
import static com.threerings.media.Log.log;
/**
* 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.
*/
public ActiveRepaintManager (Component root)
{
_root = root;
}
@Override
public synchronized void addInvalidComponent (JComponent comp)
{
Component vroot = null;
if (DEBUG) {
log.info("Maybe invalidating " + toString(comp) + ".");
}
// 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 (!c.isDisplayable() || c instanceof CellRendererPane) {
return;
}
// skip non-Swing components
if (!(c instanceof JComponent)) {
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 its 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 (vroot == null) {
if (DEBUG) {
log.info("Skipping vrootless component: " + toString(comp));
}
return;
}
// make sure that the component is actually in a window or applet that is showing
if (getRoot(vroot) == null) {
if (DEBUG) {
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
if (!ListUtil.containsRef(_invalid, vroot)) {
if (DEBUG) {
log.info("Invalidating " + toString(vroot) + ".");
}
_invalid = ListUtil.add(_invalid, vroot);
}
}
@Override
public synchronized void addDirtyRegion (JComponent comp, int x, int y, int width, int height)
{
// ignore invalid requests
if ((width <= 0) || (height <= 0) || (comp == null) ||
(comp.getWidth() <= 0) || (comp.getHeight() <= 0)) {
// Log.info("Skipping bogus region " + comp.getClass().getName() +
// ", x=" + x + ", y=" + y + ", width=" + width + ", height=" + height + ".");
return;
}
// if this component is already dirty, simply expand their existing dirty rectangle
Rectangle drect = _dirty.get(comp);
if (drect != null) {
drect.add(x, y);
drect.add(x+width, y+height);
return;
}
// make sure this component has a valid root
if (getRoot(comp) == null) {
// Log.info("Skipping rootless repaint " + comp + ".");
return;
}
drect = new Rectangle(x, y, width, height);
if (DEBUG) {
log.info("Dirtying component", "comp", toString(comp), "drect", drect);
}
// 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, drect);
}
/**
* 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
if (!RunAnywhere.isMacOS()) {
hidden |= !c.isVisible();
}
if (hidden) {
return null;
}
if (c instanceof Window || c instanceof Applet) {
return c;
}
}
return null;
}
@Override
public synchronized Rectangle getDirtyRegion (JComponent comp)
{
Rectangle drect = _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);
}
@Override
public synchronized void markCompletelyClean (JComponent comp)
{
_dirty.remove(comp);
}
/**
* Validates the invalid components that have been queued up since the last frame tick.
*/
public void validateComponents ()
{
// swap out our invalid array
Object[] invalid = null;
synchronized (this) {
invalid = _invalid;
_invalid = null;
}
// if there's nothing to validate, we're home free
if (invalid == null) {
return;
}
// validate everything therein
int icount = invalid.length;
for (int ii = 0; ii < icount; ii++) {
if (invalid[ii] != null) {
if (DEBUG) {
log.info("Validating " + invalid[ii]);
}
((Component)invalid[ii]).validate();
}
}
}
/**
* Paints the components that have become dirty since the last tick.
*
* @return true if any components were painted.
*/
public boolean paintComponents (Graphics g, FrameManager fmgr)
{
synchronized (this) {
// exit now if there are no dirty rectangles to paint
if (_dirty.isEmpty()) {
return false;
}
// otherwise, swap our hashmaps
Map tmap = _spare;
_spare = _dirty;
_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)
Iterator> iter = _spare.entrySet().iterator();
PRUNE:
while (iter.hasNext()) {
Map.Entry entry = iter.next();
JComponent comp = entry.getKey();
Rectangle drect = 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
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)) {
break;
}
// check to see if this parent is dirty
Rectangle prect = _spare.get(c);
if (prect != null) {
// 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), "drect", StringUtil.toString(drect),
"pcomp", toString(c), "prect", StringUtil.toString(prect));
}
prect.add(drect);
if (DEBUG) {
log.info("New prect " + StringUtil.toString(prect));
}
// remove the child component and be on our way
iter.remove();
continue PRUNE;
}
// translate the coordinates into this component's coordinate system
x += c.getX();
y += c.getY();
}
}
// 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()) {
Map.Entry entry = iter.next();
JComponent comp = entry.getKey();
Rectangle drect = entry.getValue();
// 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
_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
for (Component c = comp; c != null; c = c.getParent()) {
if (!c.isVisible() || !c.isDisplayable()) {
break;
}
if (c instanceof JComponent) {
// 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());
}
} else {
// 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;
break;
}
}
// translate the coordinates into this component's coordinate system
drect.x += c.getX();
drect.y += c.getY();
_cbounds.x += c.getX();
_cbounds.y += c.getY();
// clip the dirty region to the bounds of this component
SwingUtilities.computeIntersection(
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 (ocomp == null) {
ocomp = comp;
}
// 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),
"ocomp", toString(ocomp), "drect", StringUtil.toString(drect));
}
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
ocomp.paint(g);
} catch (Exception e) {
log.warning("Exception while painting component", "comp", ocomp, e);
}
g.translate(-_cbounds.x, -_cbounds.y);
// we also need to repaint any components in this layer that are above our freshly
// repainted component
fmgr.renderLayers((Graphics2D)g, ocomp, _cbounds, _clipped, drect);
} else if (root != null) {
if (DEBUG) {
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);
Graphics og = null, cg = null;
try {
og = obuf.getGraphics();
ocomp.paint(og);
cg = ocomp.getGraphics();
cg.drawImage(obuf, 0, 0, null);
} finally {
if (og != null) {
og.dispose();
}
if (cg != null) {
cg.dispose();
}
}
}
}
// clear out the mapping of dirty components
_spare.clear();
return true;
}
/**
* Used to dump a component when debugging.
*/
protected static String toString (Component comp)
{
return comp.getClass().getName() + StringUtil.toString(comp.getBounds());
}
/**
* Dumps the containment hierarchy for the supplied component.
*/
protected static void dumpHierarchy (Component comp)
{
for (String indent = ""; comp != null; indent += " ") {
log.info(indent + toString(comp));
comp = comp.getParent();
}
}
/** The root of our interface. */
protected Component _root;
/** A list of invalid components. */
protected Object[] _invalid;
/** A mapping of invalid rectangles for each widget that is dirty. */
protected Map _dirty = Maps.newHashMap();
/** A spare hashmap that we swap in while repainting dirty components in the old hashmap. */
protected Map _spare = Maps.newHashMap();
/** Used to compute dirty components' bounds. */
protected Rectangle _cbounds = new Rectangle();
/** 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! */
protected static final boolean DEBUG = false;
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy