/* * Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de) * * This program 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 3 of the License, or (at your option) * any later version. * This program 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 this program; if not, see http://www.gnu.org/licenses/ */ package com.bc.ceres.glayer.swing; import javax.swing.JComponent; import javax.swing.Timer; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Component; import java.awt.Composite; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.LayoutManager; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; /** * An animated component which can be in one of four {@link WakefulComponent.VisualState}s. * The visual state is presented by different degrees of component opacity. State transitions are * done smoothly and can be controlled by various time settings. * <p/> * <pre> * * INACTIVE | ACTIVATING | ACTIVE | DEACTIVATING | INACTIVE * |mouseEntered |mouseExited * ------------|----------------|------|-------------|------------------|---------- * |activationTime | |waitingTime |deactivationTime | * _____________________ * ___/ \_____ * ___/ \_____ * ___/ \_____ * ____________/ \__________ * * </pre> * <p/> * Clients can observe state changes by listening to changes of the * {@link WakefulComponent#getVisualState() visualState} property. * * @author Norman Fomferra * @version $Revision$ $Date$ */ public class WakefulComponent extends JComponent { private static final int PPS = 20; private float currentAlpha; private float minAlpha = 0.2f; private float maxAlpha = 0.8f; private int activationTime = 400; private int deactivationTime = 200; private int waitingTime = 700; private VisualState visualState; private long lastHitTimestamp; private final Timer timer; private final ChildResizeHandler childResizeHandler; private BufferedImage image; private Graphics2D imageGraphics; private HitHandler hitHandler; public enum VisualState { INACTIVE, ACTIVATING, ACTIVE, DEACTIVATING, } public WakefulComponent() { this(null); } public WakefulComponent(JComponent component) { setOpaque(false); childResizeHandler = new ChildResizeHandler(); visualState = VisualState.INACTIVE; timer = new Timer(0, new TimerListener()); timer.setInitialDelay(0); timer.setRepeats(true); hitHandler = new HitHandler(); currentAlpha = minAlpha; if (component != null) { add(component); final Dimension preferredSize = component.getPreferredSize(); setPreferredSize(preferredSize); installHitHandler(component); } else { installHitHandler(this); } } public int getActivationTime() { return activationTime; } public void setActivationTime(int activationTime) { this.activationTime = activationTime; } public int getDeactivationTime() { return deactivationTime; } public void setDeactivationTime(int deactivationTime) { this.deactivationTime = deactivationTime; } public int getWaitingTime() { return waitingTime; } public void setWaitingTime(int waitingTime) { this.waitingTime = waitingTime; } public float getCurrentAlpha() { return currentAlpha; } protected void setCurrentAlpha(float currentAlpha) { this.currentAlpha = currentAlpha; repaint(); } public float getMinAlpha() { return minAlpha; } public void setMinAlpha(float minAlpha) { this.minAlpha = minAlpha; } public float getMaxAlpha() { return maxAlpha; } public void setMaxAlpha(float maxAlpha) { this.maxAlpha = maxAlpha; } public VisualState getVisualState() { return visualState; } protected void setVisualState(VisualState visualState) { final VisualState oldState = getVisualState(); if (oldState != visualState) { this.visualState = visualState; firePropertyChange("visualState", oldState, visualState); } } @Override public void setBounds(int x, int y, int width, int height) { super.setBounds(x, y, width, height); if (imageGraphics != null) { imageGraphics.dispose(); } image = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_4BYTE_ABGR); imageGraphics = image.createGraphics(); imageGraphics.setBackground(new Color(0, 0, 0, 0)); } @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); for (Component component : getComponents()) { component.setEnabled(enabled); } } @Override protected void addImpl(Component comp, Object constraints, int index) { super.addImpl(comp, constraints, index); comp.addComponentListener(childResizeHandler); adaptSize(comp); installHitHandler(comp); } @Override public void remove(int index) { final Component comp = getComponent(index); comp.removeComponentListener(childResizeHandler); deinstallHitHandler(comp); super.remove(index); } @Override public final LayoutManager getLayout() { return super.getLayout(); } @Override public final void setLayout(LayoutManager mgr) { if (mgr == null) { throw new IllegalArgumentException("mgr"); } super.setLayout(mgr); } @Override public final void doLayout() { super.doLayout(); } @Override public void paint(Graphics g) { if (!isEnabled()) { return; } imageGraphics.clearRect(0, 0, image.getWidth(), image.getHeight()); final Object oldAntialias = imageGraphics.getRenderingHint(RenderingHints.KEY_ANTIALIASING); imageGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); super.paint(imageGraphics); if (oldAntialias != null) { imageGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAntialias); } Graphics2D g2d = (Graphics2D) g; Composite oldComposite = g2d.getComposite(); g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, Math.min(1.0f, currentAlpha))); g2d.drawRenderedImage(image, null); if (oldComposite != null) { g2d.setComposite(oldComposite); } } private void installHitHandler(Component comp) { comp.addMouseListener(hitHandler); comp.addMouseMotionListener(hitHandler); } private void deinstallHitHandler(Component comp) { comp.removeMouseListener(hitHandler); comp.removeMouseMotionListener(hitHandler); } private void adaptSize(Component comp) { final Rectangle r = new Rectangle(); r.add(getBounds()); r.add(comp.getBounds()); setSize(r.width, r.height); } private class HitHandler extends MouseAdapter { @Override public void mouseEntered(MouseEvent e) { // System.out.println("WakefulComponent.e = " + e); if (getVisualState() == VisualState.INACTIVE) { setVisualState(VisualState.ACTIVATING); timer.setDelay(activationTime / PPS); timer.restart(); } lastHitTimestamp = -1; } @Override public void mouseExited(MouseEvent e) { // System.out.println("WakefulComponent.e = " + e); lastHitTimestamp = System.currentTimeMillis(); } @Override public void mouseMoved(MouseEvent e) { // System.out.println("WakefulComponent.e = " + e); lastHitTimestamp = -1; } } private class TimerListener implements ActionListener { private int counter; @Override public void actionPerformed(ActionEvent e) { if (getVisualState() == VisualState.ACTIVE) { final long timestamp = System.currentTimeMillis(); if (lastHitTimestamp > 0 && timestamp - lastHitTimestamp > waitingTime) { setVisualState(VisualState.DEACTIVATING); timer.setDelay(deactivationTime / PPS); timer.restart(); } } else if (getVisualState() == VisualState.INACTIVE) { // should not come here } else { final float weight = (float) counter / (float) PPS; if (getVisualState() == VisualState.ACTIVATING) { if (counter < PPS) { setCurrentAlpha(minAlpha + (maxAlpha - minAlpha) * weight); counter++; } else { setCurrentAlpha(maxAlpha); setVisualState(VisualState.ACTIVE); counter = 0; } } else if (getVisualState() == VisualState.DEACTIVATING) { if (counter < PPS) { setCurrentAlpha(minAlpha + (maxAlpha - minAlpha) * (1 - weight)); counter++; } else { setCurrentAlpha(minAlpha); setVisualState(VisualState.INACTIVE); timer.stop(); counter = 0; } } } } } private class ChildResizeHandler extends ComponentAdapter { @Override public void componentResized(ComponentEvent e) { adaptSize(e.getComponent()); } } }