Package jfxtras.scene.menu

Source Code of jfxtras.scene.menu.CornerMenu$CornerMenuNode

package jfxtras.scene.menu;

import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicLong;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.control.MenuItem;
import javafx.scene.control.Tooltip;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.util.Duration;
import jfxtras.scene.layout.CircularPane;
import jfxtras.scene.layout.CircularPane.AnimationInterpolation;

/**
* CornerMenu is a menu is intended to be placed in one of the four corners of a pane.
* It will show the provided menu items in a 90 degree arc with the origin in the corner.
* It is possible to, and per default will, animate the menu items in and out of view.
* The showing and hiding of the menu items can be done automatically based on the mouse pointer location.
*
* CornerMenu requires a Pane to attach itself to.
* CornerMenu uses CircularPane and this will leak through in the API.
* For example: it is possible to customize the animation, and required interface to implement is the one from CircularPane.
*
* @author Tom Eugelink
*
*/
public class CornerMenu {
 
  // ==================================================================================================================
  // CONSTRUCTOR

  /**
   */
  public CornerMenu(Location location, Pane pane, boolean shown)
  {
    locationObjectProperty.set(location);
    construct(pane, shown);
  }

  /*
   *
   */
  private void construct(Pane pane, boolean shown)
  {
      this.pane = pane;
     
        // listen to items and modify circular pane's children accordingly
    getItems().addListener( (ListChangeListener.Change<? extends MenuItem> change) -> {
      while (change.next())
      {
        for (MenuItem lMenuItem : change.getRemoved())
        {
          for (javafx.scene.Node lNode : new ArrayList<javafx.scene.Node>(circularPane.getChildren())) {
            if (lNode instanceof CornerMenuNode) {
              CornerMenuNode lCornerMenuNode = (CornerMenuNode)lNode;
              if (lCornerMenuNode.menuItem == lMenuItem) {
                circularPane.remove(lCornerMenuNode);
              }
            }
          }
        }
        for (MenuItem lMenuItem : change.getAddedSubList())
        {
          circularPane.add( new CornerMenuNode(lMenuItem) );
        }
      }
        circularPane.resize(circularPane.prefWidth(-1), circularPane.prefHeight(-1));
    })
   
    // auto show and hide
        pane.addEventHandler(MouseEvent.MOUSE_MOVED, (mouseEvent) -> {
      if (isAutoShowAndHide()) {
        autoShowOrHide(mouseEvent);
      }
    });
   
      // circular pane
      setupCircularPane();
     
      // add to pane
      pane.getChildren().add(circularPane);
      circularPane.setManaged(false);
   
    // default status
    circularPane.setVisible(shown);
    setShown(shown);
    }
    private Pane pane = null;
 

  // ==================================================================================================================
  // PROPERTIES
 
  /** Location: TOP_LEFT, TOP_RIGHT, BOTTOM_RIGHT, BOTTOM_LEFT */ 
    public ReadOnlyObjectProperty<Location> locationProperty() {
      return new ReadOnlyObjectWrapper<Location>(this, "location").getReadOnlyProperty();
    }
  final private SimpleObjectProperty<Location> locationObjectProperty = new SimpleObjectProperty<Location>(this, "location", Location.TOP_LEFT);
  public static enum Location {TOP_LEFT, TOP_RIGHT, BOTTOM_RIGHT, BOTTOM_LEFT}
  public Location getLocation() { return locationObjectProperty.getValue(); }
 
  /** items */
    private final ObservableList<MenuItem> items = FXCollections.observableArrayList();
    public final ObservableList<MenuItem> getItems() {
        return items;
    }
   
  /** AutoShowAndHide: */
  public BooleanProperty autoShowAndHideProperty() { return this.autoShowAndHideObjectProperty; }
  final private SimpleBooleanProperty autoShowAndHideObjectProperty = new SimpleBooleanProperty(this, "autoShowAndHide", true);
  public Boolean isAutoShowAndHide() { return this.autoShowAndHideObjectProperty.getValue(); }
  public void setAutoShowAndHide(Boolean value) { this.autoShowAndHideObjectProperty.setValue(value); }
  public CornerMenu withAutoShowAndHide(Boolean value) { setAutoShowAndHide(value); return this; }

  /** shown */
  public final ReadOnlyBooleanProperty shownProperty() { return shown.getReadOnlyProperty(); }
    private void setShown(boolean value) { shown.set(value); }
    public final boolean isShown() { return shownProperty().get(); }
    private ReadOnlyBooleanWrapper shown = new ReadOnlyBooleanWrapper(this, "shown");
 
    // ----------------------
    // CircularPane API
   
  /** animationDuration */
  public ObjectProperty<Duration> animationDurationProperty() { return animationDurationObjectProperty; }
  final private ObjectProperty<Duration> animationDurationObjectProperty = new SimpleObjectProperty<Duration>(this, "animationDuration", Duration.millis(500));
  public Duration getAnimationDuration() { return animationDurationObjectProperty.getValue(); }
  public void setAnimationDuration(Duration value) { animationDurationObjectProperty.setValue(value); }
  public CornerMenu withAnimationDuration(Duration value) { setAnimationDuration(value); return this; }

  /** animationInterpolation: calculate the position of a node during the animation (default: move from origin), use node.relocate to position node (or manually apply layoutBounds.minX/Y) */
  public ObjectProperty<AnimationInterpolation> animationInterpolationProperty() { return animationInterpolationObjectProperty; }
  final private ObjectProperty<AnimationInterpolation> animationInterpolationObjectProperty = new SimpleObjectProperty<AnimationInterpolation>(this, "animationInterpolation", CircularPane::animateFromTheOrigin);
  public AnimationInterpolation getAnimationInterpolation() { return animationInterpolationObjectProperty.getValue(); }
  public void setAnimationInterpolation(AnimationInterpolation value) { animationInterpolationObjectProperty.setValue(value); }
  public CornerMenu withAnimationInterpolation(AnimationInterpolation value) { setAnimationInterpolation(value); return this; }



  // ==================================================================================================================
  // ACTION
 
    public void show() {
    setShown(true);
    circularPane.setVisible(true);
    circularPane.animateIn();
  }
   
    public void hide() {
    setShown(false);
    circularPane.animateOut();
    // if no animation, call the event directly
    if (circularPane.getAnimationInterpolation() == null) {
      circularPane.getOnAnimateOutFinished().handle(null);
    }
    }

  // ==================================================================================================================
  // RENDERING
 
    final private CircularPane circularPane = new CircularPane();

    /**
     *
     */
  public void removeFromPane() {
    pane.getChildren().remove(circularPane);
  }
 
    /*
     *
     */
    private void setupCircularPane() {
     
      // bind it up
      circularPane.animationDurationProperty().bind(this.animationDurationObjectProperty);
      circularPane.animationInterpolationProperty().bind(this.animationInterpolationObjectProperty);
    // circularPane.setShowDebug(javafx.scene.paint.Color.GREEN);
     
      // setup the corner we are in
    if (CornerMenu.Location.TOP_LEFT.equals(getLocation())) {
      circularPane.setStartAngle(90.0);
    }
    else if (CornerMenu.Location.TOP_RIGHT.equals(getLocation())) {
      circularPane.setStartAngle(180.0);
    }
    else if (CornerMenu.Location.BOTTOM_RIGHT.equals(getLocation())) {
      circularPane.setStartAngle(270.0);
    }
    else if (CornerMenu.Location.BOTTOM_LEFT.equals(getLocation())) {
      circularPane.setStartAngle(0.0);
    }   
    circularPane.setArc(90.0);

    // setup the position in the pane
    if (CornerMenu.Location.TOP_LEFT.equals(getLocation())) {
      circularPane.setLayoutX(0);
      circularPane.setLayoutY(0);
    }
    else if (CornerMenu.Location.TOP_RIGHT.equals(getLocation())) {
      circularPane.layoutXProperty().bind( pane.widthProperty().subtract(circularPane.widthProperty()));
      circularPane.setLayoutY(0);
    }
    else if (CornerMenu.Location.BOTTOM_RIGHT.equals(getLocation())) {
      circularPane.layoutXProperty().bind( pane.widthProperty().subtract(circularPane.widthProperty()));
      circularPane.layoutYProperty().bind( pane.heightProperty().subtract(circularPane.heightProperty()));
    }
    else if (CornerMenu.Location.BOTTOM_LEFT.equals(getLocation())) {
      circularPane.setLayoutX(0);
      circularPane.layoutYProperty().bind( pane.heightProperty().subtract(circularPane.heightProperty()));
    }
   
    // setup the animation
    circularPane.setOnAnimateOutFinished( (actionEvent) -> {
      circularPane.setVisible(false);
    });
    }

  /*
   * This class renders a MenuItem in CircularPane
   */
  private class CornerMenuNode extends Pane {
    CornerMenuNode (MenuItem menuItem) {
      this.menuItem = menuItem;
      setId(this.getClass().getSimpleName() + "#" + menuNodeIdAtomicLong.incrementAndGet());
     
      // show the graphical part
      if (menuItem.getGraphic() == null) {
        throw new NullPointerException("MenuItems in CornerMenu require a graphical part, text is optional");
      }
      getChildren().add(menuItem.getGraphic());

      // show the text as a tooltip
      if (menuItem.getText() != null && menuItem.getText().length() > 0) {
        Tooltip t = new Tooltip(menuItem.getText());
        Tooltip.install(this, t);
      }
     
      // react on a mouse click to perform the menu action
      setOnMouseClicked( (eventHandler) -> {
        if (isAutoShowAndHide()) {
          hide();
        }
        if (menuItem.getOnAction() != null) {
          menuItem.getOnAction().handle(null);
        }
      });
    }
    final private MenuItem menuItem;
  }
  private final AtomicLong menuNodeIdAtomicLong = new AtomicLong();
 
  /*
   *
   */
  private void autoShowOrHide(MouseEvent mouseEvent) {
   
    // determine distance from origin
    double lX = 0;
    double lY = 0;
    if (CornerMenu.Location.TOP_LEFT.equals(getLocation())) {
      lX = mouseEvent.getX();
      lY = mouseEvent.getY();
    }
    else if (CornerMenu.Location.TOP_RIGHT.equals(getLocation())) {
      lX = pane.getWidth() - mouseEvent.getX();
      lY = mouseEvent.getY();
    }
    else if (CornerMenu.Location.BOTTOM_RIGHT.equals(getLocation())) {
      lX = pane.getWidth() - mouseEvent.getX();
      lY = pane.getHeight() - mouseEvent.getY();
    }
    else if (CornerMenu.Location.BOTTOM_LEFT.equals(getLocation())) {
      lX = mouseEvent.getX();
      lY = pane.getHeight() - mouseEvent.getY();
    }
    lX = (lX < 0 ? 0 : lX);
    lY = (lY < 0 ? 0 : lY);
    double lDistanceFromOrigin = Math.sqrt( (lX * lX) + (lY * lY) );

    // show or hide as required
    if (lDistanceFromOrigin < 10 && circularPane.isVisible() == false && circularPane.isAnimatingIn() == false) {
      show();
    }
    if (lDistanceFromOrigin > circularPane.getWidth() && circularPane.isVisible() && circularPane.isAnimatingOut() == false) {
      hide();
    }
  }
}
TOP

Related Classes of jfxtras.scene.menu.CornerMenu$CornerMenuNode

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.