public abstract class DecoratorColor<T> extends Decorator<T, Color>{}
 public abstract class DecoratorColorGradient<T> extends DecoratorColor<T>{
	
	/** The gradient*/
	protected final Gradient gradient;
	
	/**
	 * Creates a new instance
	 * @param tiles
	 */
	public DecoratorColorGradient(final Gradient gradient){
		this.gradient = gradient;
		this.addDecoratorListener(new DecoratorListener(){
			@Override
			public void disposed() {
				gradient.dispose();
			}
		});
	}

	@Override
	public Color decorate(T element){
		return gradient.getColor(getValue(element));
	}
	
	/**
	 * Returns a value in [0, 1]
	 * @param value
	 * @return
	 */
	protected abstract double getValue(T element);
}
 public abstract class DecoratorInteger<T> extends Decorator<T, Integer>{}
 public interface DecoratorListener {

    /**
     * Called when the decorator is disposed
     */
	public abstract void disposed();
} public abstract class DecoratorString<T> extends Decorator<T, String>{}
 public interface Filter<T> {

    /**
     * Returns whether the element is accepted
     * @param t
     * @return
     */
    public boolean accepts(T t);
}
 public class Gradient {
	
	/** Tiles*/
	private final Tiles<?> tiles;
	
	/** The resulting colors*/
    private Color[] colors;
	
    /**
     * Creates a new color gradients
     * @param tiles tiles
     * @param _colors
     */
    public Gradient(final Tiles<?> tiles, Color[] _colors){
        this(tiles, _colors, 100);
    }
    
    /**
     * Creates a new color gradient
     * @param tiles tiles
     * @param _colors
     * @param steps
     */
    public Gradient(final Tiles<?> tiles, Color[] _colors, int steps){
		this.tiles = tiles;
		this.colors = getGradient(_colors, steps);
	}
    

	/**
     * Dispose all colors
     */
    public void dispose() {
    	for (Color c : colors) {
    		if (!c.isDisposed()) c.dispose();
    	}
    }


    /**
     * Returns the color array
     * @param colors
     * @param steps
     * @return
     */
    private final Color[] getGradient(Color[] colors, int steps) {
    	
    	java.awt.Color[] awtcolor = new java.awt.Color[colors.length];
    	for (int i=0; i<colors.length; i++){
    		Color color = colors[i];
    		awtcolor[i] = new java.awt.Color(color.getRed(), color.getGreen(), color.getBlue());
    	}
    	return getGradient(awtcolor, steps);
    }

    /**
     * Returns the color array
     * @param colors
     * @param steps
     * @return
     */
    private final Color[] getGradient(java.awt.Color[] colors, int steps) {

        // Draw the gradient to a buffered image
        Point2D start = new Point2D.Float(0, 0);
        Point2D end = new Point2D.Float(1, steps);
        float[] dist = new float[colors.length];
        for (int i=0; i<dist.length; i++){
            dist[i] = (1.0f / (float)dist.length) * (float)i;
        }
        LinearGradientPaint p = new LinearGradientPaint(start, end, dist, colors);
        BufferedImage legend = new BufferedImage(1,steps, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = (Graphics2D)legend.getGraphics();
        g2d.setPaint(p);
        g2d.drawRect(0,0,1,steps);
        g2d.dispose();
        
        // Convert to color array
        Color[] result = new Color[steps];
        for (int y=0; y<steps; y++){
        	int rgb = legend.getRGB(0, y);
            result[y] = new Color(this.tiles.getDisplay(), (rgb >> 16) & 0x000000ff, (rgb >> 8) & 0x000000ff, (rgb) & 0x000000ff);
        }
        
        // Return
        return result;
    }
    
    /**
	 * Returns the color for a value in [0, 1]
	 * @param value
	 * @return
	 */
	public Color getColor(double value) {
		int index = (int)Math.round((double)(colors.length-1) * value);
		return colors[index];
	}
}
 public class GradientHeatscale extends Gradient{

    /**
     * Returns the colors
     * @param tiles
     * @return
     */
	private static final Color[] getColors(Tiles<?> tiles){
	    Display device = tiles.getDisplay();
	    final Color[] colors = new Color[]{	new Color(device, 0, 0, 255), 
                	    					new Color(device, 0, 255, 255),
                	    					new Color(device, 0, 200, 0), 
                	    					new Color(device, 255, 255, 0), 
                	    					new Color(device, 255, 69, 0),
                	    					new Color(device, 255, 0, 0)};
	    
        tiles.addDisposeListener(new DisposeListener() {
            @Override
            public void widgetDisposed(DisposeEvent arg0) {
                for (Color c : colors) {
                    if (!c.isDisposed()) c.dispose();
                }
            }
        });
        return colors;
	}
	
	/**
	 * Creates a new instance
	 * @param tiles
	 */
	public GradientHeatscale(Tiles<?> tiles) {
		super(tiles, getColors(tiles));
	}
}
 public class TileLayoutDynamic extends TileLayout {

    /** Number of columns */
    private final int columns;
    /** Number of rows */
    private final int rows;

    /**
     * Creates a new instance
     * @param columns
     * @param rows
     * @param marginX
     * @param marginY
     */
    public TileLayoutDynamic(int columns, int rows, int marginX, int marginY) {
        super(marginX, marginY);
        this.columns = columns;
        this.rows = rows;
    }

    @Override
    public int getHeight(Tiles<?> tiles) {
        return (tiles.getSize().y - (rows + 1) * getMarginY()) / rows;
    }

    @Override
    public int getWidth(Tiles<?> tiles) {
        return (tiles.getSize().x - (columns + 1) * getMarginX()) / columns;
    }
}
 public class Tiles<T> extends Canvas {

    /** Current frame*/
    private Frame<T>              current           = null;

    /** Next frame*/
    private FrameStatic<T>        next              = new FrameStatic<T>(this);

    /** Default layout*/
    private TileLayout            layout            = new TileLayoutDynamic(10, 10, 5, 5);

    /** Tooltip decorator*/
    private DecoratorString<T>    tooltipDecorator  = new DecoratorStringToString<T>();

    /** Listener*/
    private PaintListener         paintListener     = getPaintListener();

    /** Listener*/
    private ResizeListener        resizeListener    = getResizeListener();

    /** Listener*/
    private MouseListener         mouseListener     = getMouseListener();

    /** Listener*/
    private MouseMoveListener     mouseMoveListener = getMouseMoveListener();

    /** Listener*/
    private DisposeListener       disposeListener   = getDisposeListener();

    /** Animation*/
    private Runnable              animation         = getAnimation();

    /** Animation settings*/
    private TileAnimationSettings settings          = new TileAnimationSettings(30, 3000);

    /** Selected item*/
    private T                     selectedItem      = null;

    /** Active item*/
    private T                     activeItem        = null;

    /** Color */
    private Color                 black;

    /**
     * Creates a new instance
     * @param parent
     * @param style
     */
    public Tiles(Composite parent, int style) {
        super(parent, style | SWT.DOUBLE_BUFFERED);
        this.addPaintListener(paintListener); 
        this.addControlListener(resizeListener);
        this.addDisposeListener(disposeListener);
        this.addMouseListener(mouseListener);
        this.addMouseMoveListener(mouseMoveListener);
        this.addControlListener(resizeListener);
        this.black = new Color(getDisplay(), 0, 0, 0);
        getDisplay().timerExec(settings.getDelta(), animation);
    }

    /**
     * Adds a selection listener
     * @param listener
     */
	public void addSelectionListener(SelectionListener listener) {
	    super.checkWidget();
		addListener(SWT.Selection, new TypedListener(listener));
	}

	/**
     * Returns the settings
     * @return
     */
    public TileAnimationSettings getAnimationSettings(){
        super.checkWidget();
        return this.settings;
    }

    /**
     * Returns the comparator
     * @return
     */
	public Comparator<T> getComparator() {
        super.checkWidget();
    	if (current == null) return null;
        else return current.getComparator();
    }

	/**
	 * Returns a decorator
	 * @return
	 */
    public DecoratorColor<T> getDecoratorBackgroundColor(){
        super.checkWidget();
    	if (current == null) return null;
        else return current.getDecoratorBackgroundColor();
    }

    /**
     * Returns a decorator
     * @return
     */
    public DecoratorColor<T> getDecoratorForegroundColor(){
        super.checkWidget();
    	if (current == null) return null;
        else return current.getDecoratorForegroundColor();
    }

    /**
     * Returns a decorator
     * @return
     */
	public DecoratorString<T> getDecoratorLabel(){
        super.checkWidget();
    	if (current == null) return null;
        else return current.getDecoratorLabel();
    }

	/**
	 * Returns a decorator
	 * @return
	 */
	public DecoratorColor<T> getDecoratorLineColor(){
        super.checkWidget();
    	if (current == null) return null;
        else return current.getDecoratorLineColor();
    }

	/**
	 * Returns a decorator
	 * @return
	 */
	public DecoratorInteger<T> getDecoratorLineStyle(){
        super.checkWidget();
    	if (current == null) return null;
        else return current.getDecoratorLineStyle();
    }

	/**
	 * Returns a decorator
	 * @return
	 */
	public DecoratorInteger<T> getDecoratorLineWidth(){
        super.checkWidget();
    	if (current == null) return null;
        else return current.getDecoratorLineWidth();
    }

	/**
	 * Returns the filter
	 * @return
	 */
	public Filter<T> getFilter() {
        super.checkWidget();
    	if (current == null) return null;
        else return current.getFilter();
    }

	/**
	 * Returns the items displayed by this widget
	 * @return
	 */
	public List<T> getItems() {
	    super.checkWidget();
		if (current == null) return null;
        else return current.getItems();
	}

	/**
	 * Returns the selected item
	 * @return
	 */
	public T getSelectedItem() {
	    super.checkWidget();
		return selectedItem;
	}

	/**
	 * Removes a selection listener
	 * @param listener
	 */
	public void removeSelectionListener(SelectionListener listener) {
	    super.checkWidget();
		removeListener(SWT.Selection, listener);
	}

	/**
     * Sets the settings
     * @return
     */
    public void setAnimationSettings(TileAnimationSettings settings){
        super.checkWidget();
        this.settings = settings;
    }
	
	@Override
	public void setBackground(Color arg0) {
	    super.checkWidget();
		super.setBackground(arg0);
		this.addFrame();
		this.next.setBackground(arg0);
	}

	/**
	 * Sets an Comparator
	 * 
	 * @param Comparator
	 */
	public void setComparator(Comparator<T> Comparator) {
	    super.checkWidget();
		this.addFrame();
		this.next.setComparator(Comparator);
	}

	/**
	 * Sets a decorator
	 * 
	 * @param decorator
	 */
	public void setDecoratorBackgroundColor(DecoratorColor<T> decorator) {
	    super.checkWidget();
		this.addFrame();
		this.next.setDecoratorBackgroundColor(decorator);
	}

	/**
	 * Sets a decorator
	 * 
	 * @param decorator
	 */
	public void setDecoratorForegroundColor(DecoratorColor<T> decorator) {
	    super.checkWidget();
		this.addFrame();
		this.next.setDecoratorForegroundColor(decorator);
	}

	/**
	 * Sets a decorator
	 * 
	 * @param decorator
	 */
	public void setDecoratorLabel(DecoratorString<T> decorator) {
	    super.checkWidget();
		this.addFrame();
		this.next.setDecoratorLabel(decorator);
	}

    /**
	 * Sets a decorator
	 * 
	 * @param decorator
	 */
	public void setDecoratorLineColor(DecoratorColor<T> decorator) {
	    super.checkWidget();
		this.addFrame();
		this.next.setDecoratorLineColor(decorator);
	}

	/**
	 * Sets a decorator
	 * 
	 * @param decorator
	 */
	public void setDecoratorLineStyle(DecoratorInteger<T> decorator) {
	    super.checkWidget();
		this.addFrame();
		this.next.setDecoratorLineStyle(decorator);
	}
    
	/**
	 * Sets a decorator
	 * 
	 * @param decorator
	 */
	public void setDecoratorLineWidth(DecoratorInteger<T> decorator) {
	    super.checkWidget();
		this.addFrame();
		this.next.setDecoratorLineWidth(decorator);
	}
    
    /**
	 * Sets a decorator
	 * 
	 * @param decorator
	 */
	public void setDecoratorTooltip(DecoratorString<T> decorator) {
	    super.checkWidget();
		this.tooltipDecorator = decorator;
	}
    
    /**
	 * Sets a filter
	 * 
	 * @param filter
	 */
	public void setFilter(Filter<T> filter) {
	    super.checkWidget();
		this.addFrame();
		this.next.setFilter(filter);
	}
	
    /**
     * Sets the selected item
     * @return
     */
    public void setSelectedItem(T item) {
        super.checkWidget();
        this.selectedItem = item;
        this.redraw();
    }
    
    /**
	 * Adds an item
	 * 
	 * @param t
	 */
	public void setItems(List<T> items) {
	    super.checkWidget();
		this.addFrame();
		this.next.setItems(items);
	}
    
    /**
	 * Sets the tile layout
	 * 
	 * @param width
	 */
	public void setTileLayout(TileLayout layout) {
	    super.checkWidget();
		this.layout = layout;
		this.addFrame();
		this.next.setTileHeight(this.layout.getHeight(Tiles.this));
		this.next.setTileWidth(this.layout.getWidth(Tiles.this));
	}

	/**
	 * Updates the tiles. 
	 * TODO: Good to override?
	 */
	@Override
	public void update() {
		
	    super.checkWidget();
		super.update();

		if (current == null){
			this.current = next.clone();
			this.current.update();
		} else if (current instanceof FrameStatic) {
			FrameStatic<T> target = next.clone();
			target.update();
			current.update();
			this.current = new FrameDynamic<T>(this, (FrameStatic<T>)current, target);
			this.current.update();
		}
	}

    /**
	 * Creates a new frame
	 */
	private void addFrame(){
		FrameStatic<T> current = this.next;
		this.next = this.next.clone();
		current.dispose();
	} 

    /**
     * Fires a selection event
     * @param tile
     */
    private void fireSelectionEvent(Tile<T> tile){
    	Event event = new Event();
    	event.data = tile.item;
    	event.display = getDisplay();
    	event.widget = this;
    	event.x = tile.x;
    	event.y = tile.y;
    	event.width = tile.width;
    	event.height = tile.height;
    	notifyListeners(SWT.Selection, event);
    }

	/**
     * Returns the animation thread
     * @return
     */
	private Runnable getAnimation() {
		return new Runnable() {
			public void run() {
				Frame<T> frame = current;
				if (frame != null && frame instanceof FrameDynamic) {
					((FrameDynamic<T>)frame).tick();
					frame.update();
					redraw();
				}
				getDisplay().timerExec(settings.getDelta(), this);
			}
		};
	}

	/**
	 * Creates a listener
	 * @return
	 */
    private DisposeListener getDisposeListener() {
		return new DisposeListener(){
            @Override
            public void widgetDisposed(DisposeEvent arg0) {
            	getDisplay().timerExec(-1, animation);
            	if (current != null) current.dispose();
            	if (next != null) next.dispose();
            	black.dispose();
                removePaintListener(paintListener);
                removeControlListener(resizeListener);
                removeMouseListener(mouseListener);
                removeMouseMoveListener(mouseMoveListener);
                removeDisposeListener(this);
            }
        };
	}

    /**
	 * Returns the mouse listener
	 * @return
	 */
    private MouseListener getMouseListener() {
		return new MouseAdapter(){
			@Override
			public void mouseDown(MouseEvent arg0) {
		        Tile<T> tile = getTileAt(arg0.x, arg0.y);
		        if (tile != null && tile.item != selectedItem) {
		        	selectedItem = tile.item;
		        	redraw();
			        fireSelectionEvent(tile);
		        } 
			}
        };
	}

    /**
	 * Returns the mouse motion listener
	 * @return
	 */
    private MouseMoveListener getMouseMoveListener() {
		return new MouseMoveListener(){
			@Override
			public void mouseMove(MouseEvent arg0) {
				
				// Check
		        Tile<T> tile = getTileAt(arg0.x, arg0.y);
		        T item = tile == null ? null : tile.item;
		        
		        // Break
		        if (item == activeItem)return;
		        
		        // Set
		        activeItem = item;
		        redraw();
		        String tooltip = activeItem != null ? tooltipDecorator.decorate(activeItem) : null;
		        Tiles.this.setToolTipText(tooltip);
			}
        };
	}

    /**
     * Creates a listener
     * @return
     */
    private PaintListener getPaintListener() {
		return new PaintListener(){
            @Override
            public void paintControl(PaintEvent e){
            	Point size = getSize();
            	paintFrame(e.gc, current, size.x, size.y);
            }
        };
	}

    /**
     * Creates a listener
     * @return
     */
    private ResizeListener getResizeListener() {
    	return new ResizeListener(this){
			@Override
			protected void controlResized() {
                addFrame();
                Point p = Tiles.this.getSize();
                next.setWidth(p.x);
                next.setHeight(p.y);
                next.setTileHeight(layout.getHeight(Tiles.this));
                next.setTileWidth(layout.getWidth(Tiles.this));
                next.setTileMarginX(layout.getMarginX());
                next.setTileMarginY(layout.getMarginY());
                update();
			}
    	};
	}

    /**
     * Returns the tile at the given location, null if there is none
     * @param x
     * @param y
     * @return
     */
    private Tile<T> getTileAt(int x, int y){
    	if (current == null) return null;
		for (Tile<T> tile : current.getTiles()) {
			if (x >= tile.x && y >= tile.y && x <= tile.x + tile.width
					&& y <= tile.y + tile.height) {
				return tile;
			}
		}
		return null;
    }

    /**
     * Paints a frame
     * @param height 
     * @param width 
     * @param decorator
     */
    private void paintFrame(GC gc, Frame<T> frame, int width, int height) {
        
        if (frame == null) {
            return; 
        }

        // Background
        gc.setBackground(frame.getBackground());
        gc.fillRectangle(0, 0, width, height);
        
        // Render each tile
        for (Tile<T> tile : frame.getTiles()) {

			// Background
			gc.setClipping(0, 0, width, height);
			
			Color backgroundColor = tile.backgroundColor;
			if (tile.item == activeItem) {
			    backgroundColor = lightDarker(tile.backgroundColor);
			} else if (tile.item == selectedItem) {
			    backgroundColor = strongDarker(tile.backgroundColor);
			}
			
			gc.setBackground(backgroundColor);
			gc.fillRectangle(tile.x, tile.y, tile.width, tile.height);

			// Border
			gc.setForeground(tile.lineColor);
			gc.setLineStyle(tile.lineStyle);
			gc.setLineWidth(tile.lineWidth);
			gc.drawRectangle(tile.x, tile.y, tile.width, tile.height);

			// Text
			gc.setForeground(tile.foregroundColor);
			gc.setClipping(tile.x, tile.y, tile.width, tile.height);
			paintString(gc, tile.label, tile.x, tile.y, tile.width, tile.height);
			
			// Dispose
			if (backgroundColor != tile.backgroundColor) {
			    gc.setBackground(tile.backgroundColor);
			    backgroundColor.dispose();
			}
        }

        // Draw border
        if ((getStyle() & SWT.BORDER) != 0) {
            gc.setForeground(gc.getDevice().getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW));
            gc.drawRectangle(0, 0, getSize().x-1, getSize().y-1);
        }
    }

    /**
     * Makes it darker
     * @param color
     * @return
     */
    private Color strongDarker(Color color) {
        int r = color.getRed() - 125;
        int g = color.getGreen() - 125;
        int b = color.getBlue() - 125;
        r = r >= 0 ? r : 0;
        g = g >= 0 ? g : 0;
        b = b >= 0 ? b : 0;
        return new Color(color.getDevice(), r, g, b);
    }

    /**
     * Makes it a little darker
     * @param color
     * @return
     */
    private Color lightDarker(Color color) {
        int r = color.getRed() - 75;
        int g = color.getGreen() - 75;
        int b = color.getBlue() - 75;
        r = r >= 0 ? r : 0;
        g = g >= 0 ? g : 0;
        b = b >= 0 ? b : 0;
        return new Color(color.getDevice(), r, g, b);
    }

    /**
     * Paints the given string within the given rectangle
     * 
     * @param gc
     * @param string
     * @param x
     * @param y
     * @param width
     * @param height
     */
    private void paintString(GC gc, String string, int x, int y, int width, int height) {
        Point extent = gc.textExtent(string);
        int xx = x + (width - extent.x) / 2;
        int yy = y + (height - extent.y) / 2;
        
        if (extent.x <= width * 0.9f) {
            gc.drawText(string, xx, yy, true);
        } else {

            // Enable anti-aliasing
            gc.setTextAntialias(SWT.ON);
            gc.setAntialias(SWT.ON);
            
            // Compute position and factor
            float factor1 = width * 0.9f / (float)extent.x;
            float factor2 = height * 0.9f / (float)extent.y;
            float factor = Math.min(factor1, factor2);
            int positionX = x + (int)(((float)width - (float)extent.x * factor) / 2f); 
            int positionY = y + (int)(((float)height - (float)extent.y * factor) / 2f);
            
            // Initialize transformation
            Transform transform = new Transform(gc.getDevice());
            transform.identity();
            transform.translate(positionX, positionY);
            transform.scale(factor, factor);
            gc.setTransform(transform);
            
            // Prepare
            Path path = new Path(this.getDisplay());
            path.addString(string, 0, 0, gc.getFont());
            
            // Draw and reset
            Color back = gc.getBackground();
            gc.setBackground(gc.getForeground());
            gc.fillPath(path);
            gc.setTransform(null);
            gc.setTextAntialias(SWT.OFF);
            gc.setAntialias(SWT.OFF);
            gc.setBackground(back);
            path.dispose();
            transform.dispose();
        }
    }

	/**
	 * Removes the current frame
	 */
	protected void removeFrame() {

		if (current instanceof FrameStatic) {
			if (!next.equals(current)) {
				update();
			} else {
			    current.update();
				redraw();
			}
		} else {
			Frame<T> old = ((FrameDynamic<T>) current);
			FrameStatic<T> source = ((FrameDynamic<T>) current).getTarget();
			current = source.clone();
			old.dispose();
			if (!next.equals(current)) {
				update();
			} else {
			    current.update();
				redraw();
			}
		}
	}
}

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;
import de.linearbits.swt.tiles.DecoratorColor;
import de.linearbits.swt.tiles.DecoratorColorGradient;
import de.linearbits.swt.tiles.DecoratorInteger;
import de.linearbits.swt.tiles.DecoratorListener;
import de.linearbits.swt.tiles.DecoratorString;
import de.linearbits.swt.tiles.Filter;
import de.linearbits.swt.tiles.Gradient;
import de.linearbits.swt.tiles.GradientHeatscale;
import de.linearbits.swt.tiles.TileLayoutDynamic;
import de.linearbits.swt.tiles.Tiles;

/* ******************************************************************************
 * Copyright (c) 2014 - 2015 Fabian Prasser.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     Fabian Prasser - initial API and implementation
 ******************************************************************************/
package de.linearbits.examples;




/**
 * Example for using the Tiles SWT Widget
 * 
 * @author Fabian Prasser
 */
public class Example1 {

    /**
     * Main entry point
     * @param args
     */
	public static void main(String[] args) {
		
		// Create shell
		Display display = new Display ();
		Shell shell = new Shell(display);
		shell.setText("SWT Tiles Example");
		shell.setLayout(new FillLayout());
	
		// Create tiles
		final Tiles<Integer> tiles = new Tiles<Integer>(shell, SWT.NONE);
		
		// Add items
		List<Integer> items = new ArrayList<Integer>();
		for (int i=0; i < 500; i++){
			items.add(i);
		}
		tiles.setItems(items);
		
		// Set layout
		tiles.setTileLayout(new TileLayoutDynamic(10, 10, 5, 5));
		
		// Order and filter

		final Filter<Integer> filter1 = getFilter1();
