package ai.cogmission.mosaic.refimpl.javafx; import java.awt.geom.Rectangle2D; import ai.cogmission.mosaic.ChangeType; import ai.cogmission.mosaic.MosaicEngine; import ai.cogmission.mosaic.MosaicEngineBuilder; import ai.cogmission.mosaic.MosaicSurfaceBuilder; import ai.cogmission.mosaic.Surface; import ai.cogmission.mosaic.SurfaceListener; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.event.EventHandler; import javafx.geometry.Bounds; import javafx.scene.Group; import javafx.scene.Node; import javafx.scene.effect.DropShadow; import javafx.scene.input.MouseEvent; import javafx.scene.layout.Region; import javafx.scene.paint.Color; /** * An implementation of a container object which is meant to be used * with the {@link MosaicEngineImpl}. * * @author David Ray * * @param <T> */ public class MosaicPane<T extends Node> extends Region { private MosaicEngine<T> layoutEngine; private Surface<T> surface; private Group content; /** * Constructs a new {@code MosaicPane} */ public MosaicPane() { this(null, null, null); } /** * Constructs a new MosaicPane using the specified * {@link MosaicEngine} and {@link Surface}. * * This constructor is used to construct a pane containing the same engine * and surface definition as a previously configured pane for copying, * serialization etc. * * @param engine the layout engine * @param surface the pre-configured surface * @param group the {@link Group} containing ui elements */ public MosaicPane(MosaicEngine<T> engine, Surface<T> surface, Group group) { if(engine == null || surface == null) { this.layoutEngine = new MosaicEngineBuilder<T>().build(); MosaicSurfaceBuilder<T> builder = new MosaicSurfaceBuilder<T>(); this.surface = builder .useIntegerPrecision(false) .cornerClickRadius(5) .useSurfaceOffset(false) .dividerSize(10) .snapDistance(15).build(); this.surface.addChangeListener(getSurfaceObserver()); content = new Group(); content.setManaged(false); getChildren().add(content); }else{ this.layoutEngine = engine; this.surface = surface; this.content = group; } layoutBoundsProperty().addListener(new ChangeListener<Bounds>() { @Override public void changed(ObservableValue<? extends Bounds> arg0, Bounds arg1, Bounds arg2) { if(arg2.getWidth() == 0 || arg2.getHeight() == 0) return; MosaicPane.this.surface.setArea(new Rectangle2D.Double(0, 0, arg2.getWidth(), arg2.getHeight())); MosaicPane.this.surface.requestLayout(); } }); addEventHandler(MouseEvent.ANY, new EventHandler<MouseEvent>() { @Override public void handle(MouseEvent evt) { if(evt.getEventType().equals(MouseEvent.MOUSE_PRESSED)) { MosaicPane.this.surface.mousePressed(evt.getX(), evt.getY()); }else if(evt.getEventType().equals(MouseEvent.MOUSE_DRAGGED)) { MosaicPane.this.surface.mouseDragged(evt.getX(), evt.getY()); }else if(evt.getEventType().equals(MouseEvent.MOUSE_RELEASED)) { MosaicPane.this.surface.mouseReleased(); } } }); } /** * Called to add an object to be laid out, to the layout engine. * * @param t the object to be laid out or key to such. * @param percentX the percentage of the overall width, the x position is located at. * @param percentY the percentage of the overall height, the y position is located at. * @param percentWidth the percentage of the overall width the object should occupy. * @param percentHeight the percentage of the overall height the object should occupy. */ public void add(T t, double percentX, double percentY, double percentWidth, double percentHeight) { surface.addRelative("", t, percentX, percentY, percentWidth, percentHeight, 0, Double.MAX_VALUE, 0, Double.MAX_VALUE); content.getChildren().add(t); } /** * Called to add an object to be laid out, to the layout engine applying the specified * String id. * * @param t the object to be laid out or key to such. * @param id the user-specified String id. * @param percentX the percentage of the overall width, the x position is located at. * @param percentY the percentage of the overall height, the y position is located at. * @param percentWidth the percentage of the overall width the object should occupy. * @param percentHeight the percentage of the overall height the object should occupy. */ public void add(T t, String id, double percentX, double percentY, double percentWidth, double percentHeight) { surface.addRelative(id, t, percentX, percentY, percentWidth, percentHeight, 0, Double.MAX_VALUE, 0, Double.MAX_VALUE); content.getChildren().add(t); } public MosaicEngine<T> getEngine() { return layoutEngine; } public Surface<T> getSurface() { return surface; } public SurfaceListener<T> getSurfaceObserver() { SurfaceListener<T> l = new SurfaceListener<T>() { public void changed(ChangeType changeType, Node n, String id, Rectangle2D r1, Rectangle2D r2) { switch(changeType) { case REMOVE_DISCARD: { content.getChildren().remove(n); requestLayout(); break; } case RESIZE_RELOCATE: { n.resizeRelocate(r2.getX(), r2.getY(), r2.getWidth(), r2.getHeight()); requestLayout(); break; } case ADD_COMMIT: { content.getChildren().add(n); n.resizeRelocate(r2.getX(), r2.getY(), r2.getWidth(), r2.getHeight()); requestLayout(); break; } case MOVE_BEGIN: { DropShadow shadow = new DropShadow(); shadow.setOffsetX(10); shadow.setOffsetY(10); shadow.setRadius(5); shadow.setColor(Color.GRAY); n.setEffect(shadow); n.toFront(); n.setOpacity(.5); break; } case RELOCATE_DRAG_TARGET: { n.resizeRelocate(r2.getX(), r2.getY(), r2.getWidth(), r2.getHeight()); requestLayout(); break; } case RESIZE_DRAG_TARGET: { n.resizeRelocate(r2.getX(), r2.getY(), r2.getWidth(), r2.getHeight()); requestLayout(); break; } case MOVE_END: { n.setOpacity(1); n.setEffect(null); break; } case ANIMATE_RESIZE_RELOCATE: { n.resizeRelocate(r2.getX(), r2.getY(), r2.getWidth(), r2.getHeight()); requestLayout(); break; } default: break; } } }; return l; } }