Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- package application;
- import javafx.application.Application;
- import javafx.beans.property.DoubleProperty;
- import javafx.beans.property.ObjectProperty;
- import javafx.beans.property.ReadOnlyObjectProperty;
- import javafx.beans.property.SimpleObjectProperty;
- import javafx.beans.value.ChangeListener;
- import javafx.beans.value.ObservableValue;
- import javafx.collections.FXCollections;
- import javafx.collections.ObservableList;
- import javafx.event.EventHandler;
- import javafx.geometry.Bounds;
- import javafx.scene.Cursor;
- import javafx.scene.Group;
- import javafx.scene.Node;
- import javafx.scene.Scene;
- import javafx.scene.control.CheckBox;
- import javafx.scene.control.Control;
- import javafx.scene.control.Label;
- import javafx.scene.control.ListView;
- import javafx.scene.control.RadioButton;
- import javafx.scene.control.ToggleGroup;
- import javafx.scene.effect.DropShadow;
- import javafx.scene.input.MouseEvent;
- import javafx.scene.layout.StackPane;
- import javafx.scene.layout.VBox;
- import javafx.scene.paint.Color;
- import javafx.scene.shape.Circle;
- import javafx.scene.shape.Line;
- import javafx.scene.shape.Rectangle;
- import javafx.scene.shape.Shape;
- import javafx.scene.shape.StrokeLineCap;
- import javafx.scene.shape.StrokeType;
- import javafx.scene.web.WebView;
- import javafx.stage.Stage;
- import javafx.stage.StageStyle;
- import javafx.stage.WindowEvent;
- /** Demo for understanding JavaFX Layout Bounds */
- public class BoundsPlayground extends Application
- {
- final ObservableList<Shape> shapes = FXCollections.observableArrayList();
- final ObservableList<ShapePair> intersections = FXCollections.observableArrayList();
- ObjectProperty<BoundsType> selectedBoundsType = new SimpleObjectProperty<BoundsType>(
- BoundsType.LAYOUT_BOUNDS );
- public static void main( final String[] args )
- {
- launch( args );
- }
- @Override
- public void start( final Stage stage )
- {
- stage.setTitle( "Bounds Playground" );
- // define some objects to manipulate on the scene.
- final Circle greenCircle = new Circle( 100, 100, 50, Color.FORESTGREEN );
- greenCircle.setId( "Green Circle" );
- final Circle redCircle = new Circle( 300, 200, 50, Color.FIREBRICK );
- redCircle.setId( "Red Circle" );
- final Line line = new Line( 25, 300, 375, 200 );
- line.setId( "Line" );
- line.setStrokeLineCap( StrokeLineCap.ROUND );
- line.setStroke( Color.MIDNIGHTBLUE );
- line.setStrokeWidth( 5 );
- final Anchor anchor1 = new Anchor( "Anchor 1", line.startXProperty(), line.startYProperty() );
- final Anchor anchor2 = new Anchor( "Anchor 2", line.endXProperty(), line.endYProperty() );
- final Group group = new Group( greenCircle, redCircle, line, anchor1, anchor2 );
- // monitor intersections of shapes in the scene.
- for ( final Node node : group.getChildrenUnmodifiable() )
- {
- if ( node instanceof Shape )
- {
- shapes.add( (Shape) node );
- }
- }
- testIntersections();
- // enable dragging for the scene objects.
- final Circle[] circles = { greenCircle, redCircle, anchor1, anchor2 };
- for ( final Circle circle : circles )
- {
- enableDrag( circle );
- circle.centerXProperty().addListener( new ChangeListener<Number>()
- {
- @Override
- public void changed( final ObservableValue<? extends Number> observableValue, final Number oldValue,
- final Number newValue )
- {
- testIntersections();
- }
- } );
- circle.centerYProperty().addListener( new ChangeListener<Number>()
- {
- @Override
- public void changed( final ObservableValue<? extends Number> observableValue, final Number oldValue,
- final Number newValue )
- {
- testIntersections();
- }
- } );
- }
- // define an overlay to show the layout bounds of the scene's shapes.
- final Group layoutBoundsOverlay = new Group();
- layoutBoundsOverlay.setMouseTransparent( true );
- for ( final Shape shape : shapes )
- {
- if ( !(shape instanceof Anchor) )
- {
- layoutBoundsOverlay.getChildren().add( new BoundsDisplay( shape ) );
- }
- }
- // layout the scene.
- final StackPane background = new StackPane();
- background.setStyle( "-fx-background-color: cornsilk;" );
- final Scene scene = new Scene( new Group( background, group, layoutBoundsOverlay ), 600, 500 );
- group.setScaleX( 5 );
- group.setScaleY( 5 );
- background.prefHeightProperty().bind( scene.heightProperty() );
- background.prefWidthProperty().bind( scene.widthProperty() );
- stage.setScene( scene );
- stage.show();
- createUtilityWindow( stage, layoutBoundsOverlay, new Shape[]{ greenCircle, redCircle } );
- }
- // update the list of intersections.
- private void testIntersections()
- {
- intersections.clear();
- // for each shape test it's intersection with all other shapes.
- for ( final Shape src : shapes )
- {
- for ( final Shape dest : shapes )
- {
- final ShapePair pair = new ShapePair( src, dest );
- if ( !(pair.a instanceof Anchor) && !(pair.b instanceof Anchor) && !intersections.contains( pair )
- && pair.intersects( selectedBoundsType.get() ) )
- {
- intersections.add( pair );
- }
- }
- }
- }
- // make a node movable by dragging it around with the mouse.
- private void enableDrag( final Circle circle )
- {
- final Delta dragDelta = new Delta();
- circle.setOnMousePressed( new EventHandler<MouseEvent>()
- {
- @Override
- public void handle( final MouseEvent mouseEvent )
- {
- // record a delta distance for the drag and drop operation.
- dragDelta.x = circle.getCenterX() - mouseEvent.getX();
- dragDelta.y = circle.getCenterY() - mouseEvent.getY();
- circle.getScene().setCursor( Cursor.MOVE );
- }
- } );
- circle.setOnMouseReleased( new EventHandler<MouseEvent>()
- {
- @Override
- public void handle( final MouseEvent mouseEvent )
- {
- circle.getScene().setCursor( Cursor.HAND );
- }
- } );
- circle.setOnMouseDragged( new EventHandler<MouseEvent>()
- {
- @Override
- public void handle( final MouseEvent mouseEvent )
- {
- circle.setCenterX( mouseEvent.getX() + dragDelta.x );
- circle.setCenterY( mouseEvent.getY() + dragDelta.y );
- }
- } );
- circle.setOnMouseEntered( new EventHandler<MouseEvent>()
- {
- @Override
- public void handle( final MouseEvent mouseEvent )
- {
- if ( !mouseEvent.isPrimaryButtonDown() )
- {
- circle.getScene().setCursor( Cursor.HAND );
- }
- }
- } );
- circle.setOnMouseExited( new EventHandler<MouseEvent>()
- {
- @Override
- public void handle( final MouseEvent mouseEvent )
- {
- if ( !mouseEvent.isPrimaryButtonDown() )
- {
- circle.getScene().setCursor( Cursor.DEFAULT );
- }
- }
- } );
- }
- // a helper enumeration of the various types of bounds we can work with.
- enum BoundsType
- {
- LAYOUT_BOUNDS, BOUNDS_IN_LOCAL, BOUNDS_IN_PARENT
- }
- // a translucent overlay display rectangle to show the bounds of a Shape.
- class BoundsDisplay extends Rectangle
- {
- // the shape to which the bounds display has been type.
- final Shape monitoredShape;
- private ChangeListener<Bounds> boundsChangeListener;
- BoundsDisplay( final Shape shape )
- {
- setFill( Color.LIGHTGRAY.deriveColor( 1, 1, 1, 0.35 ) );
- setStroke( Color.LIGHTGRAY.deriveColor( 1, 1, 1, 0.5 ) );
- setStrokeType( StrokeType.INSIDE );
- setStrokeWidth( 3 );
- monitoredShape = shape;
- monitorBounds( BoundsType.LAYOUT_BOUNDS );
- }
- // set the type of the shape's bounds to monitor for the bounds display.
- void monitorBounds( final BoundsType boundsType )
- {
- // remove the shape's previous boundsType.
- if ( boundsChangeListener != null )
- {
- final ReadOnlyObjectProperty<Bounds> oldBounds;
- switch ( selectedBoundsType.get() )
- {
- case LAYOUT_BOUNDS:
- oldBounds = monitoredShape.layoutBoundsProperty();
- break;
- case BOUNDS_IN_LOCAL:
- oldBounds = monitoredShape.boundsInLocalProperty();
- break;
- case BOUNDS_IN_PARENT:
- oldBounds = monitoredShape.boundsInParentProperty();
- break;
- default :
- oldBounds = null;
- }
- if ( oldBounds != null )
- {
- oldBounds.removeListener( boundsChangeListener );
- }
- }
- // determine the shape's bounds for the given boundsType.
- final ReadOnlyObjectProperty<Bounds> bounds;
- switch ( boundsType )
- {
- case LAYOUT_BOUNDS:
- bounds = monitoredShape.layoutBoundsProperty();
- break;
- case BOUNDS_IN_LOCAL:
- bounds = monitoredShape.boundsInLocalProperty();
- break;
- case BOUNDS_IN_PARENT:
- bounds = monitoredShape.boundsInParentProperty();
- break;
- default :
- bounds = null;
- }
- // set the visual bounds display based upon the new bounds and keep it in sync.
- if ( bounds != null )
- {
- updateBoundsDisplay( bounds.get() );
- // keep the visual bounds display based upon the new bounds and keep it in sync.
- boundsChangeListener = new ChangeListener<Bounds>()
- {
- @Override
- public void changed( final ObservableValue<? extends Bounds> observableValue,
- final Bounds oldBounds, final Bounds newBounds )
- {
- updateBoundsDisplay( newBounds );
- }
- };
- bounds.addListener( boundsChangeListener );
- }
- }
- // update this bounds display to match a new set of bounds.
- private void updateBoundsDisplay( final Bounds newBounds )
- {
- setX( newBounds.getMinX() );
- setY( newBounds.getMinY() );
- setWidth( newBounds.getWidth() );
- setHeight( newBounds.getHeight() );
- }
- }
- // an anchor displayed around a point.
- class Anchor extends Circle
- {
- Anchor( final String id, final DoubleProperty x, final DoubleProperty y )
- {
- super( x.get(), y.get(), 10 );
- setId( id );
- setFill( Color.GOLD.deriveColor( 1, 1, 1, 0.5 ) );
- setStroke( Color.GOLD );
- setStrokeWidth( 2 );
- setStrokeType( StrokeType.OUTSIDE );
- x.bind( centerXProperty() );
- y.bind( centerYProperty() );
- }
- }
- // records relative x and y co-ordinates.
- class Delta
- {
- double x, y;
- }
- // records a pair of (possibly) intersecting shapes.
- class ShapePair
- {
- private final Shape a, b;
- public ShapePair( final Shape src, final Shape dest )
- {
- a = src;
- b = dest;
- }
- public boolean intersects( final BoundsType boundsType )
- {
- if ( a == b )
- {
- return false;
- }
- a.intersects( b.getBoundsInLocal() );
- switch ( boundsType )
- {
- case LAYOUT_BOUNDS:
- return a.getLayoutBounds().intersects( b.getLayoutBounds() );
- case BOUNDS_IN_LOCAL:
- return a.getBoundsInLocal().intersects( b.getBoundsInLocal() );
- case BOUNDS_IN_PARENT:
- return a.getBoundsInParent().intersects( b.getBoundsInParent() );
- default :
- return false;
- }
- }
- @Override
- public String toString()
- {
- return a.getId() + " : " + b.getId();
- }
- @Override
- public boolean equals( final Object other )
- {
- final ShapePair o = (ShapePair) other;
- return o != null && (a == o.a && b == o.b || a == o.b && b == o.a);
- }
- @Override
- public int hashCode()
- {
- int result = a != null ? a.hashCode() : 0;
- result = 31 * result + (b != null ? b.hashCode() : 0);
- return result;
- }
- }
- // define a utility stage for reporting intersections.
- private void createUtilityWindow( final Stage stage, final Group boundsOverlay,
- final Shape[] transformableShapes )
- {
- final Stage reportingStage = new Stage();
- reportingStage.setTitle( "Control Panel" );
- reportingStage.initStyle( StageStyle.UTILITY );
- reportingStage.setX( stage.getX() + stage.getWidth() );
- reportingStage.setY( stage.getY() );
- // define content for the intersections utility panel.
- final ListView<ShapePair> intersectionView = new ListView<ShapePair>( intersections );
- final Label instructions = new Label( "Click on any circle in the scene to the left to drag it around." );
- instructions.setMinSize( Control.USE_PREF_SIZE, Control.USE_PREF_SIZE );
- instructions.setStyle( "-fx-font-weight: bold; -fx-text-fill: darkgreen;" );
- final Label intersectionInstructions =
- new Label( "Any intersecting bounds in the scene will be reported below." );
- instructions.setMinSize( Control.USE_PREF_SIZE, Control.USE_PREF_SIZE );
- // add the ability to set a translate value for the circles.
- final CheckBox translateNodes = new CheckBox( "Translate circles" );
- translateNodes.selectedProperty().addListener( new ChangeListener<Boolean>()
- {
- @Override
- public void changed( final ObservableValue<? extends Boolean> observableValue, final Boolean oldValue,
- final Boolean doTranslate )
- {
- if ( doTranslate )
- {
- for ( final Shape shape : transformableShapes )
- {
- shape.setTranslateY( 100 );
- testIntersections();
- }
- }
- else
- {
- for ( final Shape shape : transformableShapes )
- {
- shape.setTranslateY( 0 );
- testIntersections();
- }
- }
- }
- } );
- translateNodes.selectedProperty().set( false );
- // add the ability to add an effect to the circles.
- final Label modifyInstructions = new Label( "Modify visual display aspects." );
- modifyInstructions.setStyle( "-fx-font-weight: bold;" );
- modifyInstructions.setMinSize( Control.USE_PREF_SIZE, Control.USE_PREF_SIZE );
- final CheckBox effectNodes = new CheckBox( "Add an effect to circles" );
- effectNodes.selectedProperty().addListener( new ChangeListener<Boolean>()
- {
- @Override
- public void changed( final ObservableValue<? extends Boolean> observableValue, final Boolean oldValue,
- final Boolean doTranslate )
- {
- if ( doTranslate )
- {
- for ( final Shape shape : transformableShapes )
- {
- shape.setEffect( new DropShadow() );
- testIntersections();
- }
- }
- else
- {
- for ( final Shape shape : transformableShapes )
- {
- shape.setEffect( null );
- testIntersections();
- }
- }
- }
- } );
- effectNodes.selectedProperty().set( true );
- // add the ability to add a stroke to the circles.
- final CheckBox strokeNodes = new CheckBox( "Add outside strokes to circles" );
- strokeNodes.selectedProperty().addListener( new ChangeListener<Boolean>()
- {
- @Override
- public void changed( final ObservableValue<? extends Boolean> observableValue, final Boolean oldValue,
- final Boolean doTranslate )
- {
- if ( doTranslate )
- {
- for ( final Shape shape : transformableShapes )
- {
- shape.setStroke( Color.LIGHTSEAGREEN );
- shape.setStrokeWidth( 10 );
- testIntersections();
- }
- }
- else
- {
- for ( final Shape shape : transformableShapes )
- {
- shape.setStrokeWidth( 0 );
- testIntersections();
- }
- }
- }
- } );
- strokeNodes.selectedProperty().set( true );
- // add the ability to show or hide the layout bounds overlay.
- final Label showBoundsInstructions = new Label( "The gray squares represent layout bounds." );
- showBoundsInstructions.setStyle( "-fx-font-weight: bold;" );
- showBoundsInstructions.setMinSize( Control.USE_PREF_SIZE, Control.USE_PREF_SIZE );
- final CheckBox showBounds = new CheckBox( "Show Bounds" );
- boundsOverlay.visibleProperty().bind( showBounds.selectedProperty() );
- showBounds.selectedProperty().set( true );
- // create a container for the display control checkboxes.
- final VBox displayChecks = new VBox( 10 );
- displayChecks.getChildren().addAll( modifyInstructions, translateNodes, effectNodes, strokeNodes,
- showBoundsInstructions, showBounds );
- // create a toggle group for the bounds type to use.
- final ToggleGroup boundsToggleGroup = new ToggleGroup();
- final RadioButton useLayoutBounds = new RadioButton( "Use Layout Bounds" );
- final RadioButton useBoundsInLocal = new RadioButton( "Use Bounds in Local" );
- final RadioButton useBoundsInParent = new RadioButton( "Use Bounds in Parent" );
- useLayoutBounds.setToggleGroup( boundsToggleGroup );
- useBoundsInLocal.setToggleGroup( boundsToggleGroup );
- useBoundsInParent.setToggleGroup( boundsToggleGroup );
- final VBox boundsToggles = new VBox( 10 );
- boundsToggles.getChildren().addAll( useLayoutBounds, useBoundsInLocal, useBoundsInParent );
- // change the layout bounds display depending on which bounds type has been selected.
- useLayoutBounds.selectedProperty().addListener( new ChangeListener<Boolean>()
- {
- @Override
- public void changed( final ObservableValue<? extends Boolean> observableValue, final Boolean aBoolean,
- final Boolean isSelected )
- {
- if ( isSelected )
- {
- for ( final Node overlay : boundsOverlay.getChildren() )
- {
- ((BoundsDisplay) overlay).monitorBounds( BoundsType.LAYOUT_BOUNDS );
- }
- selectedBoundsType.set( BoundsType.LAYOUT_BOUNDS );
- testIntersections();
- }
- }
- } );
- useBoundsInLocal.selectedProperty().addListener( new ChangeListener<Boolean>()
- {
- @Override
- public void changed( final ObservableValue<? extends Boolean> observableValue, final Boolean aBoolean,
- final Boolean isSelected )
- {
- if ( isSelected )
- {
- for ( final Node overlay : boundsOverlay.getChildren() )
- {
- ((BoundsDisplay) overlay).monitorBounds( BoundsType.BOUNDS_IN_LOCAL );
- }
- selectedBoundsType.set( BoundsType.BOUNDS_IN_LOCAL );
- testIntersections();
- }
- }
- } );
- useBoundsInParent.selectedProperty().addListener( new ChangeListener<Boolean>()
- {
- @Override
- public void changed( final ObservableValue<? extends Boolean> observableValue, final Boolean aBoolean,
- final Boolean isSelected )
- {
- if ( isSelected )
- {
- for ( final Node overlay : boundsOverlay.getChildren() )
- {
- ((BoundsDisplay) overlay).monitorBounds( BoundsType.BOUNDS_IN_PARENT );
- }
- selectedBoundsType.set( BoundsType.BOUNDS_IN_PARENT );
- testIntersections();
- }
- }
- } );
- useLayoutBounds.selectedProperty().set( true );
- final WebView boundsExplanation = new WebView();
- boundsExplanation
- .getEngine()
- .loadContent(
- "<html><body bgcolor='darkseagreen' fgcolor='lightgrey' style='font-size:12px'><dl>"
- + "<dt><b>Layout Bounds</b></dt><dd>The boundary of the shape.</dd><br/>"
- + "<dt><b>Bounds in Local</b></dt><dd>The boundary of the shape and effect.</dd><br/>"
- + "<dt><b>Bounds in Parent</b></dt><dd>The boundary of the shape, effect and transforms.<br/>The co-ordinates of what you see.</dd>"
- + "</dl></body></html>" );
- boundsExplanation.setPrefWidth( 100 );
- boundsExplanation.setMinHeight( 130 );
- boundsExplanation.setMaxHeight( 130 );
- boundsExplanation.setStyle( "-fx-background-color: transparent" );
- // layout the utility pane.
- final VBox utilityLayout = new VBox( 10 );
- utilityLayout
- .setStyle( "-fx-padding:10; -fx-background-color: linear-gradient(to bottom, lightblue, derive(lightblue, 20%));" );
- utilityLayout.getChildren().addAll( instructions, intersectionInstructions, intersectionView,
- displayChecks, boundsToggles, boundsExplanation );
- utilityLayout.setPrefHeight( 530 );
- reportingStage.setScene( new Scene( utilityLayout ) );
- reportingStage.show();
- // ensure the utility window closes when the main app window closes.
- stage.setOnCloseRequest( new EventHandler<WindowEvent>()
- {
- @Override
- public void handle( final WindowEvent windowEvent )
- {
- reportingStage.close();
- }
- } );
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement