package com.yoursway.experiments.birdseye; import static com.yoursway.swt.additions.YsSwtGeometry.divideIntoHorizontalParts; import static com.yoursway.swt.additions.YsSwtGeometry.divideIntoVerticalParts; import static com.yoursway.swt.additions.YsSwtGeometry.emptyRectangle; import static com.yoursway.swt.additions.YsSwtGeometry.isSameSize; import static com.yoursway.utils.Listeners.newListenersByIdentity; import static java.lang.Math.max; import static java.lang.Math.min; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.PaletteData; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import com.yoursway.experiments.birdseye.component.EmptyNode; import com.yoursway.experiments.birdseye.model.Compartment; import com.yoursway.experiments.birdseye.model.Container; import com.yoursway.experiments.birdseye.model.Leaf; import com.yoursway.experiments.birdseye.model.Node; import com.yoursway.utils.Listeners; public class BirdsEyeComposite extends Composite { private BirdsEyeCompositePrivates privates = new BirdsEyeCompositePrivates(); private Image buffer; private Node root; private Leaf hovered; private transient Listeners<BirdsEyeListener> listeners = newListenersByIdentity(); public synchronized void addListener(BirdsEyeListener listener) { listeners.add(listener); } public synchronized void removeListener(BirdsEyeListener listener) { listeners.remove(listener); } private class BirdsEyeCompositePrivates implements Listener { public void handleEvent(Event event) { switch (event.type) { case SWT.Paint: paint(event); break; case SWT.Resize: regenerate(); break; case SWT.MouseMove: handleHovering(event); break; case SWT.MouseExit: handleHoveringEnd(event); break; } } } public BirdsEyeComposite(Composite parent, int style) { super(parent, style | SWT.NO_BACKGROUND); addListener(SWT.Paint, privates); addListener(SWT.Resize, privates); addListener(SWT.MouseMove, privates); addListener(SWT.MouseExit, privates); } void handleHovering(Event event) { Point point = new Point(event.x, event.y); Leaf hovered = root.pick(point); changeHovered(hovered, event); } void handleHoveringEnd(Event event) { changeHovered(null, event); } private void changeHovered(Leaf hovered, Event event) { if (hovered == this.hovered) return; this.hovered = hovered; for (BirdsEyeListener listener : listeners) listener.birdsEyeHovered(hovered, event); } void paint(Event event) { if (buffer == null) generate(); if (buffer != null) event.gc.drawImage(buffer, 0, 0); } public void display(Node root) { if (root == null) throw new NullPointerException("root is null"); this.root = root; regenerate(); } private void regenerate() { if (root == null) return; generate(); redraw(); } private void generate() { Rectangle clientArea = getClientArea(); if (clientArea.width < 2 || clientArea.height < 2) { if (buffer != null) buffer.dispose(); buffer = null; return; } if (buffer == null || !isSameSize(clientArea, buffer.getBounds())) { if (buffer != null) buffer.dispose(); buffer = new Image(getDisplay(), clientArea.width, clientArea.height); } GC gc = new GC(buffer); try { gc.setBackground(getBackground()); gc.fillRectangle(clientArea); drawNode(gc, root, clientArea); } finally { gc.dispose(); } } private void drawNode(GC gc, Node node, Rectangle area) { node.setRectangle(area); if (node instanceof Leaf) drawLeaf(gc, (Leaf) node, area); else if (node instanceof Compartment) drawContainer(gc, (Compartment) node, area); else if (node instanceof EmptyNode) { drawEmpty(gc, area); } else drawNode(gc, ((Container) node).child(), area); } private void drawContainer(GC gc, Compartment compartment, Rectangle area) { Node first = compartment.first(); Node second = compartment.second(); double totalSize = compartment.size(); double ratio = first.size() / totalSize; Rectangle firstArea = emptyRectangle(); Rectangle secondArea = emptyRectangle(); if (area.width > area.height) divideIntoVerticalParts(area, ratio, firstArea, secondArea); else divideIntoHorizontalParts(area, ratio, firstArea, secondArea); drawNode(gc, first, firstArea); drawNode(gc, second, secondArea); } private void drawLeaf(GC gc, Leaf leaf, Rectangle area) { gc.setBackground(leaf.color()); gc.fillRectangle(area); gc.drawRectangle(area); drawGradient(gc, area, leaf.color()); } private void drawEmpty(GC gc, Rectangle area) { gc.setBackground(gc.getDevice().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND)); gc.fillRectangle(area); gc.drawRectangle(area); } public static void drawGradient(GC gc, Rectangle area, Color color) { if (area.width == 0 || area.height == 0) return; if (area.width == 1 && area.height == 1) { gc.setBackground(gc.getDevice().getSystemColor(SWT.COLOR_BLACK)); gc.drawPoint(area.x, area.y); return; } float[] hsb = color.getRGB().getHSB(); PaletteData paletteData = new PaletteData(0x00FF0000, 0x0000FF00, 0x000000FF); int colors[] = new int[256]; // darker colors for (int i = 0; i < 128; i++) { float adjust = 0.5f * (128 - i) / 128.0f; RGB rgb = new RGB(hsb[0], hsb[1], hsb[2] * (1 - adjust)); colors[i] = paletteData.getPixel(rgb); } // lighter colors for (int i = 0; i < 128; i++) { float adjust = 0.5f * i / 128.0f; // first ramp up brightness, then decrease saturation float dif = 1 - hsb[2]; float absAdjust = (dif + hsb[1]) * adjust; RGB rgb; if (absAdjust < dif) rgb = new RGB(hsb[0], hsb[1], hsb[2] + absAdjust); else rgb = new RGB(hsb[0], hsb[1] + dif - absAdjust, 1.0f); colors[128 + i] = paletteData.getPixel(rgb); } int width = area.width; int height = area.height; ImageData data = new ImageData(width, height, 24, paletteData); int x0 = 0, y0 = 0; // Horizontal lines for (int y = 0; y < height; y++) { int gradient = (int) (256 * (y + 0.5f) / area.height + 0.5); int intColor = colors[min(255, max(0, gradient))]; int xend = (height - y - 1) * width / height; // Maximum x. for (int x = 0; x < xend; x++) data.setPixel(x, y, intColor); } // Vertical lines for (int x = 0; x < width; x++) { int gradient = (int) (256 * (1 - (x + 0.5f) / area.width) + 0.5); int intColor = colors[min(255, max(0, gradient))]; int ystart = (width - x - 1) * height / width; // Minimum y. for (int y = ystart; y < height; y++) data.setPixel(x, y, intColor); } Image image = new Image(gc.getDevice(), data); gc.drawImage(image, area.x, area.y); image.dispose(); } }