
org.pushingpixels.lafwidget.contrib.blogofbug.swing.layout.CaroselLayout Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of laf-widget Show documentation
Show all versions of laf-widget Show documentation
Laf-Widget provides support for common "feel" widgets in look-and-feel libraries
The newest version!
/*
* CaroselLayout.java
*
* Created on November 21, 2006, 11:48 AM
*
* Copyright 2006-2007 Nigel Hughes
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at http://www.apache.org/
* licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
package org.pushingpixels.lafwidget.contrib.blogofbug.swing.layout;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.*;
import javax.swing.Timer;
import org.pushingpixels.lafwidget.contrib.blogofbug.swing.components.RichComponent;
/**
* Layout engine for JCarousel components (although would work for any container).
* It does have a closer than usual relationship with the container, sometimes causing the container to repaint.
* @author bug
*/
public class CaroselLayout implements LayoutManager,ActionListener{
/**
* Number of items in the carousel (that are visible)
*/
protected int numberOfItems = 0;
/**
* List of components being laid out
*/
protected LinkedList components = new LinkedList();
/**
* List of additional information held on components in the carousel
*/
protected Hashtable additionalData = new Hashtable();
/**
* The current degree of rotation of the carousel
*/
protected double rotationalOffset = 0.0;
/**
* The desired rotational offset, which will be moved to by a timer animating the carousel
*/
protected double targetOffset = 0.0;
/**
* Time for driving animations
*/
private Timer animationTimer = null;
/**
* The container the layout is... laying out
*/
private Container container = null;
/**
* Should items furtehr away from the observer be faded out?
*/
private boolean depthBasedAlpha = true;
/**
* The width of a component when the scale is 1.0
*/
private int neutralContentWidth = 64;
/**
* Creates a new instance of the layout engine, tied to the specified container.
* @param forContainer The container the layout will layout
*/
public CaroselLayout(Container forContainer){
animationTimer = new Timer(0,this);
container = forContainer;
}
/**
* Specify the neutral content width of any laid out component.
* @param neutralContentWidth The neutral width of components
*/
public void setNeutralContentWidth(int neutralContentWidth) {
this.neutralContentWidth = neutralContentWidth;
}
/**
* Moves a layout component at a particular location in the
* carousel
*
* @param i The location at which to insert
* @param comp The component to insert
*
*/
public void moveComponentTo(int i, Component comp){
components.remove(comp);
components.add(i,comp);
recalculateCarosel();
}
/**
* Name is ignored
* @param name The name of the component, ignored.
* @param comp The component being added
*/
public void addLayoutComponent(String name, Component comp) {
components.addLast(comp);
recalculateCarosel();
}
/**
* Remove the component
* @param comp The component being removed
*/
public void removeLayoutComponent(Component comp) {
components.remove(comp);
recalculateCarosel();
}
/**
* Gets the additional data stored by the layout manager for a given component
*
* @param comp The component you wish retreive the data for
* @return A position, which is added if it does not already exist. Never null unless
* you run out of memory!
*/
protected CaroselPosition getPosition(Component comp){
CaroselPosition cpos = (CaroselPosition) additionalData.get(comp);
if (cpos==null){
cpos = new CaroselPosition(comp);
additionalData.put(comp,cpos);
}
return cpos;
}
/**
* Determines how many of the items being laid out are currently visible.
* @return How many of the items in the carousel are currently visible.
*/
protected int recalculateVisibleItems(){
int visibleItems=0;
try{
for (Component comp : components){
if (comp.isVisible()){
visibleItems++;
}
}
} catch (ConcurrentModificationException ex){
return recalculateVisibleItems();
}
return visibleItems;
}
/**
* Updates all of the positions of the carousel. Does not do a repaint, just does the math ready for the next one.
*/
protected void recalculateCarosel(){
//Need to count visible, not just how many in the list
//Again dealing with out-of-EDT modification
numberOfItems = recalculateVisibleItems();
//Trap and re-calc on concurrent modification (might as well be up-to-date)
try{
boolean animate=false;
double itemCount = 0;
for (Component comp : components){
CaroselPosition position = getPosition(comp);
if (comp.isVisible()){
double localAngle = itemCount * (Math.PI * 2.0 / (double) numberOfItems);
position.setAngle(localAngle);
}
if (position.isAnimating()){
animate=true;
}
itemCount+=1.0;
}
//If we do need to animate, get it started
if (animate){
animationTimer.start();
}
} catch (ConcurrentModificationException ex){
recalculateCarosel();
return;
}
}
/**
* Cheats and bases it's size on the prefered sizes of each component
* @param parent The container interested in the layout size
* @return The minimum size of the layout. See above.
*/
public Dimension minimumLayoutSize(Container parent) {
return preferredLayoutSize(parent);
}
/**
* Determine the widest and tallest dimensions, then return the height as 1.5 * the highest, and 3 * the widest
* @param parent The container for the layout
* @return The prefered size of the layout
*/
public Dimension preferredLayoutSize(Container parent) {
Dimension dim = new Dimension(0, 0);
// get widest preferred width for left && right
// get highest preferred height for left && right
// add preferred width of middle
int widestWidth = 0;
int highestHeight = 0;
Iterator i = components.iterator();
while (i.hasNext()){
Component comp = (Component) i.next();
if (comp.isVisible()){
widestWidth = Math.max(widestWidth, comp.getPreferredSize().width);
highestHeight = Math.max(highestHeight, comp.getPreferredSize().height);
}
}
dim.width = widestWidth * 3;
dim.height = highestHeight * 2;
Insets insets = parent.getInsets();
dim.width += insets.left + insets.right;
dim.height += insets.top + insets.bottom;
return dim;
}
/**
* Determines the center of the carousel
* @param insets The insets of the container
* @param width The width of the container
*
* @param height The height of the container
* @param widest The widest component
* @return A point at the center of the carousel
*/
protected Point calculateCenter(Insets insets, int width, int height, int widest){
return new Point((insets.left+widest/2) + width/2, insets.top + height/2);
}
/**
* Controls if items should fade as they move to the back of the carousel
* @param depthBasedAlpha True if they should fade, false if they shouldn't
*/
public void setDepthBasedAlpha(boolean depthBasedAlpha) {
this.depthBasedAlpha = depthBasedAlpha;
}
/**
* Can be over-ridden to restrict the range of angles where the child component
* is shown
* @return false if the component should not be shown
* @param comp Controls if components are hidden or not, in the case of this layout it always returns false
* @param angle The angle of the component under consideration
* @param s The scale of the component under consideration
*/
protected boolean shouldHide(Component comp, double angle, double s){
if (depthBasedAlpha){
if (comp instanceof RichComponent){
s = Math.min(1.0f,Math.max(0.0f,s/1.2f));
((RichComponent) comp).setAlpha((float)s);
}
}
return false;
}
/**
* Determines the correct size of the carousel for the container
* @param target The target container
* @param insets Insets into the target container
* @param width Width of the target container
* @param height Height of the target container
* @param widestComponent The widest component in the container
* @return The
*/
protected Dimension getCarouselRadius(Container target, Insets insets, int width, int height, int widestComponent){
return null;
}
/**
* Determines the scale to be applied to the component. The default calculation
* divides the y co-ordinate by the y-cordinate of the centre. Other implimentations
* may use some of the other parameters
*
* @param angle The angle of the component
* @param x The x-position of the component
* @param y The y-position of the component
* @param carouselX The x centre of the carousel
* @param carouselY The y centre of the carousel
* @return A double which will be used to scale x and y co-ordinates
*/
protected double getScale(double angle, double x, double y, double carouselX, double carouselY){
return (y / carouselY);
}
/**
* Lays out all of the components on the carosel. Using the preferred width and height to base
* scaling on
* @param target The container currently being laid out
*/
public void layoutContainer(Container target) {
//Let's make a local copy of components to avoid concurrent modification
//which could happen if someone adds something to the layout outside
//of the EDT. This is faster than do any synchronization or brute force
//exception catching
LinkedList components = (LinkedList) this.components.clone();
int numberOfItems = this.numberOfItems;
recalculateCarosel();
// these variables hold the position where we can draw components
// taking into account insets
Insets insets = target.getInsets();
int width = target.getSize().width - (insets.left + insets.right);
int height = target.getSize().height - (insets.top + insets.bottom);
//No longer calculate the width based on prefered sizes, we're going to control
//it by the component not the content'
int widestWidth = neutralContentWidth;
int highestHeight = 0;
width -= widestWidth;
int radiusX = width /2;
int radiusY = radiusX/3;
Dimension radius = getCarouselRadius(target,insets,width,height,widestWidth);
if (radius!=null){
radiusX = radius.width;
radiusY = radius.height;
}
Point center = calculateCenter(insets,width,height,widestWidth);
int centerX = center.x;
int centerY = center.y;
//Go through each visible component and set the scale and z-order, and eventually the bounds
//Need to protected against other things adding components at the same time
Iterator i = components.iterator();
int p = 0;
CaroselPosition z_order[] = new CaroselPosition[numberOfItems];
while (i.hasNext()){
Component comp = (Component) i.next();
CaroselPosition position = getPosition(comp);
double finalAngle = position.getAngle()+this.rotationalOffset;
double x = (Math.sin(finalAngle) * (double) radiusX)+(double) centerX;
double y = (Math.cos(finalAngle) * (double) radiusY)+(double) centerY;
double initialWidth = (double) comp.getPreferredSize().width;
double initialHeight = ((double) comp.getPreferredSize().height) * (initialWidth / (double) comp.getPreferredSize().width);
double s = getScale(finalAngle, x,y,(double) centerX, (double) centerY);//(y / (double) centerY);
double boundsWidth = initialWidth * s;
double boundsHeight = initialHeight * s;
if (!shouldHide(comp, finalAngle,s)){
//Even scaling only to avoid windows jitter...
int finalWidth = (int)boundsWidth / 1;
int finalHeight = (int)boundsHeight / 1;
finalWidth = (int) finalWidth & 0xFFFFFFFE;
finalHeight = (int) finalHeight & 0xFFFFFFFE;
comp.setBounds((int)x - ((int)boundsWidth/2),(int) y - ((int)boundsHeight /2),finalWidth, finalHeight);
} else {
comp.setBounds(-100,-100,32,32);
}
position.setZ(s);
z_order[p++] = position;
}
//Now sort out the z, we may need to cache the dimensions, do the z and then reset the bounds, see what happens on redraw first
//bubble sort is actually very fast for a small number of items, and this layout shouldn't be used for loads.
boolean swaps = true;
int limit = numberOfItems-1;
while (swaps){
swaps = false;
for (int j=0;j Math.PI){
if (target=components.size()){
return components.get(0);
} else {
return components.get(i);
}
}
/**
* The number of components being laid out. Does not included hidden ones
* @return The number of components
*/
public int getComponentCount(){
return components.size();
}
/**
* Gets the index of the supplied component
* @param comp The component
* @return The index
*/
public int getComponentIndex(Component comp) {
return components.indexOf(comp);
}
/**
* The size of comopnents a neutral width
* @return The size of components at neutral width (scale 1.0)
*/
public int getNeutralContentWidth() {
return neutralContentWidth;
}
class CaroselPosition{
protected double angle;
protected double scale;
protected double z;
protected Component component;
protected boolean firstSet = false;
protected double targetAngle = 0.0;
public CaroselPosition(Component component){
angle = 0.0;
scale = 0.0;
z = 0.0;
this.component = component;
}
public Component getComponent(){
return component;
}
public double getZ(){
return z;
}
public void setZ(double z){
this.z = z;
}
public double getTargetAngle(){
return targetAngle;
}
public double getAngle(){
return angle;
}
public double getScale(){
return scale;
}
public boolean isAnimating(){
if ((Math.abs(angle - targetAngle) < 0.001)){
return false;
}
return true;
}
public void moveToTarget(){
angle=targetAngle;
}
public void updateAngle(){
if ((Math.abs(angle - targetAngle) < 0.001)){
angle = targetAngle;
} else {
angle += Math.min((targetAngle - angle) / 6.0,0.10);
}
}
public void setAngle(double angle){
if (firstSet){
this.angle = angle;
this.targetAngle = angle;
firstSet = false;
} else {
this.targetAngle = angle;
}
}
public void setScale(double scale){
this.scale = scale;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy