アクセスカウンタ

help RSS テーマ「JavaFX」のブログ記事

みんなの「JavaFX」ブログ


The JavaFX Properties binding(4) - Gesture Event

2013/02/13 01:19

The JavaFX Properties binding for computing the area of triangle(4) - enabling Gesture Event Handling



This article is a continuation of The JavaFX Properties binding for computing the area of triangle(3).

The goal of this part is the handling GestureEvents, specially TouchEvent, ZoomEvent and RotateEvent.

The handler for TouchEvent can handle at only three points multi touch occurs at the same time. When three point multi touch, the three small circles are drawn at the touch points, and three lines are stroked to connect between each pair of two circles. It will construct a triangle.

The handle for ZoomEvent and RotateEvent can handle at two points multi touch occurs. The rotate handler handle rotation of the triangle on its center of gravity.The zoom handler change a radius of the circles represents the vertexes of the triangle, and thickness of the lines represents the sides of the triangle.It dose not change the size of the triangle.

I had some difficulty doing both GestureEvent and MouseEvent deal with in a proper way, such as, after the ZoomEvent or RotateEvent handlers are done, almost all case, the MouseEvent handler also performs. It is quite difference from what I expected. So, a couple of codes are added in the handler of MouseEvent, which is If the isSynthesized() method return true, then consume() the event.

My runtime environment is that Splashtop Win8 Metro Testbed on iPad2 is used at Remote Desktop Side as Multi Touch device, and Splashtop Streamer on Windwos 8 Pro on SONY VAIO note PC is used for the program running.

The tools of Splashtop was introduced by @aoetk on his LT session at Study Meeting for JavaFX in Japan on 2012/11/30.

Enabling Rotate, Zoom and MultiTouch Gestures


In addition, for debugging, the TextField and Slider is added on the right bottom area. The TextField is for rotation angle, and Slider is extension ratio of zooming. These controls are introduced for the easy testing and debugging of the handler logics on the environment without multitouch support.

Here is the code. It's so long.

Since a lot of white space for indentation is cause the exceed the limit of the length for a single blog article, there are wide width white space characters for indentation, and there are no comments. If you copy and paste the following code, and compile it, then javac will complain of illegal characters in the source code.

import java.util.List;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.NumberBinding;
import javafx.beans.binding.When;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.GroupBuilder;
import javafx.scene.Scene;
import javafx.scene.SceneBuilder;
import javafx.scene.control.Label;
import javafx.scene.control.LabelBuilder;
import javafx.scene.control.Slider;
import javafx.scene.control.SliderBuilder;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFieldBuilder;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.RotateEvent;
import javafx.scene.input.TouchEvent;
import javafx.scene.input.TouchPoint;
import javafx.scene.input.ZoomEvent;
import javafx.scene.layout.HBoxBuilder;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.CircleBuilder;
import javafx.scene.shape.Line;
import javafx.scene.shape.LineBuilder;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.RectangleBuilder;
import javafx.stage.Stage;
import javafx.util.StringConverter;

public class MultiTouch1 extends Application {
 final double PPI = 96.0;
 final double INCHI = 2.54;
 final double PPC = PPI/INCHI;

 final int WIDTH = 800;
 final int HEIGHT = 600;
 final int MARGIN = 5;
 final int OFFSET = 5;

 final int INNER_WIDTH = WIDTH - MARGIN * 2;
 final int INNER_HEIGHT = HEIGHT - MARGIN * 2;

 final int ORIGIN_X = MARGIN + OFFSET;
 final int ORIGIN_Y = INNER_HEIGHT - OFFSET;

 final double THIN_STROKE = 0.1;
 final double THICK_STROKE = 0.3;

 private int clickcount = 0;

 private Rectangle rectangle;
 private Circle c[] = new Circle[3];
 private Line l[] = new Line[3];
 private Line a_x_l[] = new Line[(int)(INNER_HEIGHT/PPC)];
 private Line a_y_l[] = new Line[(int)(INNER_WIDTH/PPC)];
 private TextField tx[] = new TextField[3];
 private TextField ty[] = new TextField[3];
 private TextField tr;
 private Label area_Label = new Label();
 private Slider slider;
 private Circle gp_c = CircleBuilder.create()
  .radius(0.0).fill(Color.RED).build();

 private DoubleProperty org_x[] =
  new SimpleDoubleProperty[3];

 private DoubleProperty org_y[]
   new SimpleDoubleProperty[3];

 private NumberBinding map_x[] = new NumberBinding[3];
 private NumberBinding map_y[] = new NumberBinding[3];
 private NumberBinding area;
 private NumberBinding gp_x;
 private NumberBinding gp_y;

 private DoubleProperty rotation_angle =
  new SimpleDoubleProperty(0.0);

 private Color color[] = { Color.RED, Color.BLUE, Color.GREEN };

 {
  rectangle = RectangleBuilder.create()
   .layoutX(MARGIN).layoutY(MARGIN)
   .width(INNER_WIDTH).height(INNER_HEIGHT)
   .fill(Color.LIGHTYELLOW)
   .build();

  slider = SliderBuilder.create()
   .min(0.0).max(10.0).value(1.0)
   .showTickMarks(true).showTickLabels(true)
   .majorTickUnit(1.0).blockIncrement(0.5)
   .orientation(Orientation.HORIZONTAL)
   .build();

  tr = TextFieldBuilder.create()
   .prefColumnCount(4)
   .text("0.0")
   .onAction(new EventHandler<ActionEvent>() {
    @Override public void handle(ActionEvent e) {
     double angle = Math.toRadians(
      Double.parseDouble(
       ((TextField)e.getSource()).getText()));
     rotation_angle.set(angle%360.0);
     rotation(angle);
    }
   })
   .build();

  for(int i = 0; i < 3; i++ ) {
   c[i] = CircleBuilder.create()
    .radius(0.0).fill(color[i]).build();

   l[i] = LineBuilder.create()
    .strokeWidth(0.0).stroke(Color.BLACK).build();

   org_x[i] = new SimpleDoubleProperty(ORIGIN_X);
   org_y[i] = new SimpleDoubleProperty(ORIGIN_Y);
   map_x[i] = org_x[i].subtract(ORIGIN_X).divide(PPC);
   map_y[i] = org_y[i].negate().add(ORIGIN_Y).divide(PPC);
   c[i].radiusProperty().bind(
       Bindings.multiply(slider.valueProperty(), 10.0));

   l[i].strokeWidthProperty().bind(
       Bindings.multiply(slider.valueProperty(), 5.0));

   tx[i] = TextFieldBuilder.create()
    .prefColumnCount(2).build();

   ty[i] = TextFieldBuilder.create()
    .prefColumnCount(2)build();
  }    

  for (int i = 0; i < a_x_l.length; i++) {
   double delta_y = (INNER_HEIGHT - 5) - (i+1) * (PPC);
   a_x_l[i] = new Line(
       MARGIN, delta_y, MARGIN + INNER_WIDTH, delta_y);
   a_x_l[i].setStrokeWidth(
          (i+1)%5==0 ? THICK_STROKE : THIN_STROKE);
  }

  for (int i = 0; i < a_y_l.length; i++) {
   double delta_x = (MARGIN + 5) + (i+1) * (PPC);
   a_y_l[i] = new Line(
      delta_x, MARGIN, delta_x, MARGIN + INNER_HEIGHT);
   a_y_l[i].setStrokeWidth(
          (i+1)%5==0 ? THICK_STROKE : THIN_STROKE);
  }

  gp_x = org_x[0].add(org_x[1]).add(org_x[2]).divide(3.0);
  gp_y = org_y[0].add(org_y[1]).add(org_y[2]).divide(3.0);

  gp_c.centerXProperty().bind(gp_x);
  gp_c.centerYProperty().bind(gp_y);
  gp_c.radiusProperty().bind(
        Bindings.min(slider.valueProperty(), 2.0));
 }

 StringConverter<Number> sc_x =
  new StringConverter<Number>() {
   @Override public Number fromString(String from) {
    double map_x = Double.parseDouble(from);
    double org_x = (MARGIN + 5) + (map_x*PPC);
    return new Double(org_x);
   }

   @Override public String toString(Number org_x) {
    double map_x = (org_x.doubleValue() - (MARGIN + 5))/PPC;
    return String.format("%2.1f", map_x);
   }
 };

 StringConverter<Number> sc_y =
  new StringConverter<Number>() {
   @Override public Number fromString(String from) {
    double map_y = Double.parseDouble(from);
    double org_y = (INNER_HEIGHT - 5) - (map_y*PPC);
    return new Double(org_y);
   }

   @Override public String toString(Number org_y) {
    double map_y = ((INNER_HEIGHT - 5) - org_y.doubleValue())/PPC;
    return String.format("%2.1f", map_y);
   }
 };

 StringConverter<Number> sc_angle =
  new StringConverter<Number>() {
   @Override public Number fromString(String from) {
    double angle = Double.parseDouble(from);
    double r_angle = Math.toRadians(angle%360.0);
    return new Double(r_angle);
   }

   @Override public String toString(Number r_angle) {
    double angle = Math.toDegrees(r_angle.doubleValue());
    return String.format("%2.1f", (angle%360.0));
   }
 };

 public MultiTouch1() {
  initialize();
    
  for(int i = 0; i < 3; i++) {
   int j = (i == 2) ? 0 : i+1;

   l[i].startXProperty().bind(c[i].centerXProperty());
   l[i].startYProperty().bind(c[i].centerYProperty());
   l[i].endXProperty().bind(c[j].centerXProperty());
   l[i].endYProperty().bind(c[j].centerYProperty());

   Bindings.bindBidirectional(
            c[i].centerXProperty(), org_x[i]);
   Bindings.bindBidirectional(
            c[i].centerYProperty(), org_y[i]);
   Bindings.bindBidirectional(
        tx[i].textProperty(), c[i].centerXProperty(), sc_x);
   Bindings.bindBidirectional(
        ty[i].textProperty(), c[i].centerYProperty(), sc_y);
   Bindings.bindBidirectional(
        tr.textProperty(), rotation_angle, sc_angle);
  }

  NumberBinding tmp_area =
   map_x[0].multiply(map_y[1])
    .add(map_x[1].multiply(map_y[2]))
    .add(map_x[2].multiply(map_y[0]))
    .subtract(map_x[0].multiply(map_y[2]))
    .subtract(map_x[1].multiply(map_y[0]))
    .subtract(map_x[2].multiply(map_y[1]))
    .divide(2.0);

  area = new When(tmp_area.lessThan(0))
       .then(tmp_area.negate())
       .otherwise(tmp_area);
    
  area_Label.textProperty()
   .bind(new SimpleStringProperty("Area = ")
    .concat(area.asString("%2.1f")));
 }

 private void initialize() {
  clickcount = 0;
 }

 @Override public void start(Stage stage) {
  final Group root;    
  Scene scene = SceneBuilder.create()
   .width(WIDTH).height(HEIGHT)
   .fill(Color.WHITE)
   .root(root = GroupBuilder.create()
   .children(rectangle)
   .build()
  ).build();
   
  root.getChildren().addAll(a_x_l);
  root.getChildren().addAll(a_y_l);
    
  root.getChildren().addAll(
   LineBuilder.create()
    .startX(MARGIN).startY(ORIGIN_Y)
    .endX(MARGIN + INNER_WIDTH).endY(ORIGIN_Y)
    .build(),
   LineBuilder.create()
    .startX(ORIGIN_X).startY(MARGIN)
    .endX(ORIGIN_X).endY(MARGIN + INNER_HEIGHT)
    .build()
  );

  root.getChildren().addAll(
   HBoxBuilder.create()
    .spacing(3).padding(new Insets(10, 10, 10, 10))
    .layoutX(10)
    .alignment(Pos.BOTTOM_CENTER)
    .children(
     LabelBuilder.create().text("A(").build(),
     tx[0],
     LabelBuilder.create().text(",").build(),
     ty[0],
     LabelBuilder.create().text("),").build(),
     LabelBuilder.create().text("B(").build(),
     tx[1],
     LabelBuilder.create().text(",").build(),
     ty[1],
     LabelBuilder.create().text("),").build(),
     LabelBuilder.create().text("C(").build(),
     tx[2],
     LabelBuilder.create().text(",").build(),
     ty[2],
     LabelBuilder.create().text(") ⇒ ").build(),
     area_Label
    )
    .build(),
   HBoxBuilder.create()
    .layoutX(INNER_WIDTH - 270).layoutY(INNER_HEIGHT - 30)
    .children(
     LabelBuilder.create().text("Rotate").build(),
     tr,
     LabelBuilder.create().text("Zoome").build(),
     slider
    )
    .build()
   );

  rectangle.setOnMouseClicked(new EventHandler<MouseEvent>() {
   @Override public void handle(MouseEvent e) {
    if(e.isSynthesized()) { e.consume(); return; }
    if (clickcount > 2) { initialize(); }

    final int i = clickcount;
    final double x = e.getSceneX();
    final double y = e.getSceneY();

    Task<Void> task = new Task<Void>() {
     @Override public Void call() {
      Platform.runLater(new Runnable() {
       @Override public void run() {
        if(i == 0) {
         root.getChildren()
           .removeAll(l[0], l[1], l[2], c[0], c[1], c[2], gp_c);
        }
        org_x[i].set(x);
        org_y[i].set(y);
        switch(i) {
         case 0:
          root.getChildren().add(c[0]);
          break;
         case 1:
          root.getChildren().remove(c[0]);
          root.getChildren().addAll(l[0], c[0], c[1]);
          break;
         case 2:
          root.getChildren().removeAll(c[0], c[1], l[0]);
          root.getChildren()
            .addAll(l[0], l[1], l[2], c[0], c[1], c[2], gp_c);
          break;
         default :
        }
       }
      });
      return null;
     }
    };
    new Thread(task).start();
    clickcount++;
    e.consume();
   }
  });

  rectangle.setOnTouchPressed(new EventHandler<TouchEvent>() {
   @Override public void handle(TouchEvent e) {
    int tc = e.getTouchCount();
    if(tc != 3) { e.consume(); return; }
    final List<TouchPoint> tps = e.getTouchPoints();
    Platform.runLater(new Runnable() {
     @Override public void run() {
      root.getChildren().removeAll(
          l[0], l[1], l[2], c[0], c[1], c[2], gp_c);
      int i = 0;
      for(TouchPoint p : tps) {
       org_x[i].set(p.getSceneX());
       org_y[i].set(p.getSceneY());
       i++;
      }
      root.getChildren().addAll(
          l[0], l[1], l[2], c[0], c[1], c[2], gp_c);
     }
    });
    clickcount += tc;
    e.consume();
   }
  });

  rectangle.setOnZoom(new EventHandler<ZoomEvent> (){
   @Override public void handle(ZoomEvent e) {
    double scale = e.getZoomFactor()
             * slider.valueProperty().get();
    slider.valueProperty().set(scale);
    e.consume();
   }
  });

  rectangle.setOnRotate(new EventHandler<RotateEvent>() {
   @Override public void handle(RotateEvent e) {
    double angle = e.getAngle()/20.0;
    rotation_angle.set(e.getTotalAngle()%360.0);
    rotation(angle);
    e.consume();
   }
  });

  stage.setTitle("Triangle Area");
  stage.setScene(scene);
  stage.show();
 }

 private void rotation(double angle) {
  double o_x[] = new double[3];
  double o_y[] = new double[3];
  double r_x[] = new double[3];
  double r_y[] = new double[3];
  double g_x = gp_x.doubleValue();
  double g_y = gp_y.doubleValue();
  for(int i = 0; i < 3; i++) {
   o_x[i] = org_x[i].get();
   o_y[i] = org_y[i].get();
   r_x[i] = (o_x[i]-g_x)*Math.cos(angle
        - (o_y[i]-g_y)*Math.sin(angle) + g_x;
   r_y[i] = (o_x[i]-g_x)*Math.sin(angle)
        + (o_y[i]-g_y)*Math.cos(angle) + g_y;
  }
  for(int i = 0; i < 3; i++) {
   org_x[i].set(r_x[i]);
   org_y[i].set(r_y[i]);
  }
 }

 public static void main(String[] args) {
  launch(args);
 }
}


Another notice is that the above program can resize the window size, but the its own Coordinate area is not expanded.

This is the end of the series of the JavaFX Properties binding.
記事へブログ気持玉 / トラックバック / コメント


The JavaFX Properties binding(3)

2013/01/31 00:29

The JavaFX Properties binding for computing the area of triangle(3)



This article is a continuation of The JavaFX Properties binding for computing the area of triangle(2).

The goals of this part are that
  1. make it possible to input the values of the both x-coordinates and y-coordinates of three vertices of triangle into the TextFields which were introduced at the previous part,
  2. reflect the location the circles and lines for three vertices and sides to the inputted values.

The current code is the property binding is unidirectional, the centerX and centerY properties for the vertices is bound to the text properties of the TextFields. Now, It is nice way for the goals to make the bindings direction changes to bidirectional.

Here is the code fragments for the change to the bidirectional binding.

First of all, two import statements as follows are added on the current code.

import javafx.util.StringConverter;
import javafx.beans.binding.Bindings;


Then, two StringConverter instances are created. They convert the type and value representing the vertices. One is for x-coordinates and, the other is for y-coordinates.

The converter has two methods, which are fromString() and toString().

The fromString() method is perform the type and value conversions the String value in TextField to double value of the pixel position on the scene. The toString() method is perform the type and value conversion the double value of the pixel position on the scene to String value in the TextField.

Here is the implementation code.

  StringConverter<Number> sc_x =
   new StringConverter<Number>() {
    @Override public Number fromString(String from) {
      double map_x = Double.parseDouble(from);
      double org_x = 10 + (map_x*96/2.54);
      return new Double(org_x);
    }
    @Override public String toString(Number org_x) {
      double map_x = (org_x.doubleValue()-10)*2.54/96;
      return String.format("%2.1f", map_x);
    }
  };

  StringConverter<Number> sc_y =
   new StringConverter<Number>() {
    @Override public Number fromString(String from) {
      double map_y = Double.parseDouble(from);
      double org_y = 490 - (map_y*96/2.54);
      return new Double(org_y);
    }
    @Override public String toString(Number org_y) {
      double map_y = (490 - org_y.doubleValue())*2.54/96;
      return String.format("%2.1f", map_y);
    }
  };

  // change the class name to TriangleArea2
  public TriangleArea2() {
    initialize();

    for(int i = 0; i < 3; i++) {
      int j = (i == 2) ? 0 : i+1;

      l[i].startXProperty().bind(c[i].centerXProperty());
      l[i].startYProperty().bind(c[i].centerYProperty());
      l[i].endXProperty().bind(c[j].centerXProperty());
      l[i].endYProperty().bind(c[j].centerYProperty());

      // bind bidirectional
      // between the clicked position and
      // the center[X|Y]Property of Circle
      Bindings.bindBidirectional(
        c[i].centerXProperty(), org_x[i]);
      Bindings.bindBidirectional(
        c[i].centerYProperty(), org_y[i]);

      // bind bidirectional
      // between the center[X|Y]Property of Circle and
      // textProperty of TextField with StringConverter
      Bindings.bindBidirectional(
        tx[i].textProperty(), c[i].centerXProperty(), sc_x);
      Bindings.bindBidirectional(
        ty[i].textProperty(), c[i].centerYProperty(), sc_y);
    }

    // ...


be able to specify the vertices by both mouse click and input the value in TextField

Inputing the value in TextField, such as shown by the yellow circle in above figure, then the position of Circle and Line is reflected by the input value.

Even with intention of clicking on the intersection point of vertical and horizontal lines, the clicked position might be off the exact point of the intersection. In such situation, the TextField for inputs are useful to the adjust the positions.

Now, we reach the goal of this part.

What shall we do next part ?

The new idea is that it allow you to make touch three points on the screen at the same time. The multi touch gesture events might be suitable for it.

To be continued.
記事へブログ気持玉 / トラックバック / コメント


The JavaFX Properties binding(2)

2013/01/19 00:46

The JavaFX Properties binding for computing the area of triangle(2)




This article is a continuation of The JavaFX Properties binding for computing the area of triangle(1).

The goals of this part are that
  1. prints the values of the both x-coordinates and y-coordinates of three vertices of triangle out in the TextFields and a label at the top of the Scene,
  2. prints the result of the computing the area of triangle out on a label at the next of the TextFields at the top of the Scene,
  3. make its own Cartesian plane, which Origin is near bottom-left of the Scene, and
  4. show 1 centimeter interval grid on the Screen.


The Origin(O) of its own Cartesian plane is located at the point of 5 pixel right and 5 pixel above from the left-bottom corner with leaving another 5 pixel margin(top, bottom, left and right). The x-axis and y-axis lines through the Origin are drawn by thick lines. The grid interval is 1 centimeter(about 37.8pixels/cm = 96pixel/inch). The lines of grid is drawn by thin lines. The both x-coordinates and y-coordinates of three vertices is printed out with "%2.1f" format in the TextFields at the top of the Scene, because the area for printing out is limited spaces.

Here is the code which implements these things.

import javafx.application.Application;
import javafx.beans.binding.NumberBinding;
import javafx.beans.binding.When;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.GroupBuilder;
import javafx.scene.Scene;
import javafx.scene.SceneBuilder;
import javafx.scene.control.Label;
import javafx.scene.control.LabelBuilder;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFieldBuilder;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBoxBuilder;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.CircleBuilder;
import javafx.scene.shape.Line;
import javafx.scene.shape.LineBuilder;
import javafx.scene.shape.RectangleBuilder;
import javafx.stage.Stage;

public class TriangleArea1 extends Application {
  private int clickcount = 0;
  private Circle c[] = new Circle[3];
  private Line l[] = new Line[3];
  private Line a_x_l[] = new Line[12];
  private Line a_y_l[] = new Line[12];
  private TextField tx[] = new TextField[3];
  private TextField ty[] = new TextField[3];
  private Label area_Label = new Label();

  private DoubleProperty org_x[] = new SimpleDoubleProperty[3];
  private DoubleProperty org_y[]= new SimpleDoubleProperty[3];
  private NumberBinding map_x[] = new NumberBinding[3];
  private NumberBinding map_y[] = new NumberBinding[3];
  private NumberBinding area;

  {
    for(int i = 0; i < 3; i++ ) {
      // create Circles represented the vertices of triangle
      // and Lines represented the sides of triangle
      c[i] = CircleBuilder.create()
                          .radius(3.0)
                          .fill(Color.RED)
                          .build();
      l[i] = LineBuilder.create()
                          .strokeWidth(2.0)
                          .stroke(Color.RED)
                          .build();

      // create TextFields for print out the x-coordinates
      // and y-coordinates of the vertices
      tx[i] = TextFieldBuilder.create()
                              .prefColumnCount(2)
                              .build();
      ty[i] = TextFieldBuilder.create()
                              .prefColumnCount(2)
                              .build();

      // Properties for the mouse clicked position
      // Origin position by pixel from top-left of the scene
      org_x[i] = new SimpleDoubleProperty(10);
      org_y[i] = new SimpleDoubleProperty(490);

      // the coordinates of its own Cartesian plane
      // bound by the Properties of mouse clicked position
      map_x[i] = org_x[i].subtract(10)
                         .multiply(2.54)
                         .divide(96.0);
      map_y[i] = org_y[i].negate()
                         .add(490)
                         .multiply(2.54)
                         .divide(96.0);
    }

    // lines for the grid
    for (int i = 0; i < 12; i++) {
      double delta_y = 490.0 - (i+1) * (96.0 / 2.54);
      double delta_x = 10.0 + (i+1) * (96.0 / 2.54);
      a_x_l[i] = new Line(5, delta_y, 495, delta_y);
      a_x_l[i].setStrokeWidth((i+1)%5==0 ? 0.3 : 0.1);
      a_y_l[i] = new Line(delta_x, 5, delta_x, 495);
      a_y_l[i].setStrokeWidth((i+1)%5==0 ? 0.3 : 0.1);
    }
  }

  public TriangleArea1() {
    initialize();

    for(int i = 0; i < 3; i++) {
      int j = (i == 2) ? 0 : i+1;

      // Start and end positions of Line is bound by
      // the vertices(the center position of Circles)
      l[i].startXProperty().bind(c[i].centerXProperty());
      l[i].startYProperty().bind(c[i].centerYProperty());
      l[i].endXProperty().bind(c[j].centerXProperty());
      l[i].endYProperty().bind(c[j].centerYProperty());

      // center position of Circle is bound by
      // the mouse clicked position
      c[i].centerXProperty().bind(org_x[i]);
      c[i].centerYProperty().bind(org_y[i]);

      // Text of TextField is bound by the string form
      // presenting the vertices(the center of Circles)
      tx[i].textProperty().bind(map_x[i].asString("%2.1f"));
      ty[i].textProperty().bind(map_y[i].asString("%2.1f"));
    }

    // the area of triangle is computed by the bound values
    // by Circle positions
    NumberBinding tmp_area =
             map_x[0].multiply(map_y[1])
        .add(map_x[1].multiply(map_y[2]))
        .add(map_x[2].multiply(map_y[0]))
        .subtract(map_x[0].multiply(map_y[2]))
        .subtract(map_x[1].multiply(map_y[0]))
        .subtract(map_x[2].multiply(map_y[1]))
        .divide(2.0);

    // the code for computing the absolute value using
    // Class When and then() and otherwise() methods.
    area = new When(tmp_area.lessThan(0))
              .then(tmp_area.negate())
              .otherwise(tmp_area);

    // Text of Label presenting the area of triangle is
    // bound by the string form the computing the area
    area_Label.textProperty().bind(
      new SimpleStringProperty("Area = ")
              .concat(area.asString("%2.1f")));
  }

  private void initialize() {
    clickcount = 0;
  }

  @Override public void start(Stage stage) {
    final Group root;
    Scene scene = SceneBuilder.create()
      .width(500).height(500)
      .fill(Color.WHITE)
      .root(root = GroupBuilder.create()
        .children(RectangleBuilder.create()
          .layoutX(5).layoutY(5)
          .width(490).height(490)
          .fill(Color.LIGHTSKYBLUE)
          .onMouseClicked(new EventHandler<MouseEvent>() {
            // Eventhandler for mouse click event
            @Override public void handle(MouseEvent e) {
              if (clickcount > 2) { initialize(); }
              for (int j = clickcount; j < 3; j++) {
                org_x[j].set(e.getSceneX());
                org_y[j].set(e.getSceneY());
              }
              clickcount++;
            }
          }).build(),
        // line for x-axis through the Origin
        LineBuilder.create()
          .startX(5).startY(490)
          .endX(495).endY(490)
          .build(),
        // Line for y-axis through the Origin
        LineBuilder.create()
          .startX(10).startY(5)
          .endX(10).endY(495)
          .build(),
        // grid horizontal lines which interval is of 1cm
        a_x_l[0], a_x_l[1], a_x_l[2], a_x_l[3],
        a_x_l[4], a_x_l[5], a_x_l[6], a_x_l[7],
        a_x_l[8], a_x_l[9], a_x_l[10], a_x_l[11],
        // grid vertical lines which interval is of 1cm
        a_y_l[0], a_y_l[1], a_y_l[2], a_y_l[3],
        a_y_l[4], a_y_l[5], a_y_l[6], a_y_l[7],
        a_y_l[8], a_y_l[9], a_y_l[10], a_y_l[11],
        // Sides
        l[0], l[1], l[2],
        // Vertices
        c[0], c[1], c[2],
        // the printing space of the coordinates and
        // the area of triangle
        HBoxBuilder.create()
          .layoutX(10)
          .padding(new Insets(10, 10, 10, 10)).spacing(3)
          .alignment(Pos.BOTTOM_CENTER)
          .children(
            LabelBuilder.create().text("A(").build(),
            tx[0],
            LabelBuilder.create().text(",").build(),
            ty[0],
            LabelBuilder.create().text("),").build(),
            LabelBuilder.create().text("B(").build(),
            tx[1],
             LabelBuilder.create().text(",").build(),
            ty[1],
            LabelBuilder.create().text("),").build(),
            LabelBuilder.create().text("C(").build(),
            tx[2],
            LabelBuilder.create().text(",").build(),
            ty[2],
            LabelBuilder.create().text(") ⇒ ").build(),
            area_Label
          ).build()
      ).build()
    ).build();

    stage.setTitle("Triangle Area");
    stage.setScene(scene);
    stage.show();
  }

  public static void main(String[] args) {
    launch(args);
  }
}


print the vertices and area out on the top of Scene


The code is so long, isn't it?

It might be due to many Builder classes are used for building Nodes with a lots of properties. In this case, the FXML could be useful.

Anyway, the mission accomplished.
The implementation of the doing print the values of three vertices and the area of triangle out on the scene is done.

The next goal is that the three vertices can be also inputted on the TextFields introduced in this time. And, of course, the circles and lines will be adjusted the location to the inputted values.

... To be continued
記事へブログ気持玉 / トラックバック / コメント


The JavaFX Properties binding(1)

2013/01/12 12:47

The JavaFX Properties binding for computing the area of triangle(1)




As I wrote a previous article,
I found that JavaFX program using Properties' binding is in the Chapter 3, the book of "JavaFX 2 Pro".

That is the code that the triangle area can be computed by the following fomula,

Area = |(x1*y2 + x2*y3 + x3*y1 - x1*y3 - x2*y1 - x3*y2)| / 2

where the three vertices is at (x1, y1), (x2, y2), (x3, y3) on Cartesian plane.

Here is the source code, which is almost same as the code of Listing 3-6 in the JavaFX 2 Pro book.

public class TriangleAreaFluentExample {
  public static void main(String[] args) {
    IntegerProperty x1 = new SimpleIntegerProperty(0);
    IntegerProperty y1 = new SimpleIntegerProperty(0);
    IntegerProperty x2 = new SimpleIntegerProperty(0);
    IntegerProperty y2 = new SimpleIntegerProperty(0);
    IntegerProperty x3 = new SimpleIntegerProperty(0);
    IntegerProperty y3 = new SimpleIntegerProperty(0);

    final NumberBinding area =
                      x1.multiply(y2)
                        .add(x2.multiply(y3))
                        .add(x3.multiply(y1))
                        .subtract(x1.multiply(y3))
                        .subtract(x2.multiply(y1))
                        .subtract(x3.multiply(y2))
                        .divide(2.0D);

    StringExpression output = Bindings.format(
      "3点 A(%d,%d), B(%d,%d), C(%d,%d) " +
            "の三角形の面積は %3.1f",
                  x1, y1, x2, y2, x3, y3, area);

    x1.set(0); y1.set(0);
    x2.set(6); y2.set(0);
    x3.set(4); y3.set(3);
    System.out.println(output.get());

    x1.set(1); y1.set(0);
    x2.set(2); y2.set(2);
    x3.set(0); y3.set(1);
    System.out.println(output.get());

    x1.set(10); y1.set(10);
    x2.set(2); y2.set(2);
    x3.set(5); y3.set(1);
    System.out.println(output.get());
  }
}

The result of colculation of triangles area print out on console


The first time I saw the code, It is really interesting.
It's JavaFX code, but it's has no UI Controls !!

Now, I would like to add some UIs on the code.

First of all, I think it's a sort of ideas anyone can come up with,
trying to giving the three vertices by mouse clicking on the screen,
instead of by program code.

A small Circle(like a dot) is appears at the mouse clicked points, it's at most 3 points.
And also, The Line is drawn between each small circles.
Therefore, there are three Circles and Three Lines.

Some DoubleProperties introduced into the code as two of three elements of arrays.
One array is for x-coordinate and the other array is for y-coordinate of the three vertices,
which are setted the value of the SceneX and SceneY of MouseEvents raised when mouse is clicked.

To show up small Circle at the mouse clicked,
the Circle's Propertes centerX and centerY is bound to the DoubleProperties.
Also, To draw Line between two Circles,
the Line' Properties startX, startY, endX and endY is bound to the two Circle's Property centerX and centerY.

After the 3 mouse clicked occures, then the area of triangle is computed.
The code for computaion of the area is almost same as original code.
The extra code for getting the absolute value is added.
It use the When class and its then() and otherwise() methoed.
It's interesting class and methods as well.

Here is the source code.

import javafx.application.Application;
import javafx.beans.binding.NumberBinding;
import javafx.beans.binding.When;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.GroupBuilder;
import javafx.scene.Scene;
import javafx.scene.SceneBuilder;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.shape.LineBuilder;
import javafx.scene.shape.RectangleBuilder;
import javafx.stage.Stage;

public class TriangleArea0 extends Application {
  private int clickcount = 0;
  private Circle c[] = new Circle[3];
  private Line l[] = new Line[3];
  private DoubleProperty x[] = new SimpleDoubleProperty[3];
  private DoubleProperty y[] = new SimpleDoubleProperty[3];

  private NumberBinding area;

  {
    for(int i = 0; i < 3; i++ ) {
        c[i] = new Circle();
        l[i] = new Line();
        x[i] = new SimpleDoubleProperty(0);
        y[i] = new SimpleDoubleProperty(0);
    }
  }

  public TriangleArea0() {
    initialize();

    for(int i = 0; i < 3; i++) {
      int j = (i == 2) ? 0 : i+1;

      l[i].startXProperty().bind(c[i].centerXProperty());
      l[i].startYProperty().bind(c[i].centerYProperty());
      l[i].endXProperty().bind(c[j].centerXProperty());
      l[i].endYProperty().bind(c[j].centerYProperty());

      c[i].centerXProperty().bind(x[i]);
      c[i].centerYProperty().bind(y[i]);
    }

    NumberBinding
      tmp_area = x[0].multiply(y[1])
            .add(x[1].multiply(y[2]))
            .add(x[2].multiply(y[0]))
            .subtract(x[0].multiply(y[2]))
            .subtract(x[1].multiply(y[0]))
            .subtract(x[2].multiply(y[1]))
            .divide(2.0);

    area = new When(tmp_area.lessThan(0))
              .then(tmp_area.negate())
              .otherwise(tmp_area);
  }

  private void initialize() {
    clickcount = 0;
  }

  private void print_area() {
    System.out.println(
        "A = (" + x[0].get() + ", " + y[0].get() + ")");
    System.out.println(
        "B = (" + x[1].get() + ", " + y[1].get() + ")");
    System.out.println(
        "C = (" + x[2].get() + ", " + y[2].get() + ")");
    System.out.println("Area = " + area.doubleValue());
  }

  @Override public void start(Stage stage) {
    final Group root;
    Scene scene = SceneBuilder.create()
      .width(500).height(500)
      .fill(Color.WHITE)
      .root(root = GroupBuilder.create()
        .children(RectangleBuilder.create()
          .layoutX(5).layoutY(5)
          .width(490).height(490)
          .fill(Color.LIGHTSKYBLUE)
          .onMouseClicked(new EventHandler<MouseEvent>() {
            @Override public void handle(MouseEvent e) {
              if (clickcount > 2) { initialize(); }
              for (int j = clickcount; j < 3; j++) {
                x[j].set(e.getSceneX());
                y[j].set(e.getSceneY());
              }
              clickcount++;
              if (clickcount == 3) { print_area(); }
            }
        }).build(),
        LineBuilder.create()
          .startX(5).startY(490)
          .endX(495).endY(490)
          .build(),
        LineBuilder.create()
          .startX(10).startY(5)
          .endX(10).endY(495)
          .build(),
        l[0], l[1], l[2],
        c[0], c[1], c[2]
      ).build()
    ).build();

    stage.setTitle("Triangle Area");
    stage.setScene(scene);
    stage.show();
  }

  public static void main(String[] args) {
    launch(args);
  }
}
The virtices is given by mose clicks


While the the code is running,
the first mouse click is indicated the first vertix is at the clicked potision,
and the small circle is shown at the position of the screen.
With a second click, other small circle is shown at the position,
and also the Line between the centers of 2 small circles is drawn.
With a third click, another small circle is shown at the position,
and the two other Lines are drawn.
The area of triangle is printed out the standard output.
The value of Area is expressed in terms of square pixels.

The occuring of fourth click is handling with the same way as the occuring the first click.

This code dose not output the area of triangle on the UI Screen. Next, try to output the area of triangle on the screen

(due to the limitation of the number of characters in a article) to be cotinuted ...
記事へブログ気持玉 / トラックバック / コメント


JavaFX の Bind で三角形の面積を求める... の続き(4) - マルチタッチに挑戦

2012/12/30 02:47
If you prefer to read this article in ENGLISH, go to [in English] page.

JavaFX の Bind で三角形の面積を求める... の続き(3) の続き。

とりあえず、ジェスチャイベントに対応させてみた。

三角形を指定するマルチタッチは 3 点固定のみ受け付けるようした。
3点をタッチすると、その位置に円を、円と円とのあいたを結んだ線が描画される。

そして、ズームと回転のジェスチャにも対応。2点のマルチタッチで動く。回転は、三角形の重心を中心にして回転させている。ズームは、頂点の円の大きさと、辺の線の太さを変えている。三角形の大きさは変えていない。

面倒だったのは、ムーズと回転をさせたあと、多くの場合に、MouseClicck のイベントハンドラが動いてしまうこと。そこで isSynthesized() が true のときは、イベント消費だけするようにしたら、改善された。

ちなみに、Windows 8 Pro のリモートデスクトップ環境で動作させた。具体的には、Windwos 8 Pro + Splashtop Streamer, iPad + Splashtop Win8 Metro Testbed。Splashtop は、2012/11/30 の JavaFX 勉強会の LT で @aoetk さんがデモで利用し、紹介されたツール。

回転とズーム、マルチタッチに対応した


おまけに、右下に回転角度と、ズーム率を、それぞれ変えるためのテキストフィールドとスライダも用意した。これは、マルチタッチ環境でなくても、ロジックの正しさを確認する)ために(デバッグしやすいように)仕込んだもの。回転のロジックは、同じメソッド(rotation)を呼び出している。

ソースはこれ ... 長い ....

今は、コメントを書いていないけど、後で追加するかも。

それより、インデントを &nbsp; でやると、文字数オーバーで投稿できない。pre タグも使えないので、全角空白文字で行っているので、Copy&Paste されるかたはご注意を。

import java.util.List;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.NumberBinding;
import javafx.beans.binding.When;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.GroupBuilder;
import javafx.scene.Scene;
import javafx.scene.SceneBuilder;
import javafx.scene.control.Label;
import javafx.scene.control.LabelBuilder;
import javafx.scene.control.Slider;
import javafx.scene.control.SliderBuilder;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFieldBuilder;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.RotateEvent;
import javafx.scene.input.TouchEvent;
import javafx.scene.input.TouchPoint;
import javafx.scene.input.ZoomEvent;
import javafx.scene.layout.HBoxBuilder;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.CircleBuilder;
import javafx.scene.shape.Line;
import javafx.scene.shape.LineBuilder;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.RectangleBuilder;
import javafx.stage.Stage;
import javafx.util.StringConverter;

public class MultiTouch1 extends Application {
 final double PPI = 96.0;
 final double INCHI = 2.54;
 final double PPC = PPI/INCHI;

 final int WIDTH = 800;
 final int HEIGHT = 600;
 final int MARGIN = 5;
 final int OFFSET = 5;

 final int INNER_WIDTH = WIDTH - MARGIN * 2;
 final int INNER_HEIGHT = HEIGHT - MARGIN * 2;

 final int ORIGIN_X = MARGIN + OFFSET;
 final int ORIGIN_Y = INNER_HEIGHT - OFFSET;

 final double THIN_STROKE = 0.1;
 final double THICK_STROKE = 0.3;

 private int clickcount = 0;

 private Rectangle rectangle;
 private Circle c[] = new Circle[3];
 private Line l[] = new Line[3];
 private Line a_x_l[] = new Line[(int)(INNER_HEIGHT/PPC)];
 private Line a_y_l[] = new Line[(int)(INNER_WIDTH/PPC)];
 private TextField tx[] = new TextField[3];
 private TextField ty[] = new TextField[3];
 private TextField tr;
 private Label area_Label = new Label();
 private Slider slider;
 private Circle gp_c = CircleBuilder.create()
  .radius(0.0).fill(Color.RED).build();

 private DoubleProperty org_x[] =
  new SimpleDoubleProperty[3];

 private DoubleProperty org_y[]
   new SimpleDoubleProperty[3];

 private NumberBinding map_x[] = new NumberBinding[3];
 private NumberBinding map_y[] = new NumberBinding[3];
 private NumberBinding area;
 private NumberBinding gp_x;
 private NumberBinding gp_y;

 private DoubleProperty rotation_angle =
  new SimpleDoubleProperty(0.0);

 private Color color[] = { Color.RED, Color.BLUE, Color.GREEN };

 {
  rectangle = RectangleBuilder.create()
   .layoutX(MARGIN).layoutY(MARGIN)
   .width(INNER_WIDTH).height(INNER_HEIGHT)
   .fill(Color.LIGHTYELLOW)
   .build();

  slider = SliderBuilder.create()
   .min(0.0).max(10.0).value(1.0)
   .showTickMarks(true).showTickLabels(true)
   .majorTickUnit(1.0).blockIncrement(0.5)
   .orientation(Orientation.HORIZONTAL)
   .build();

  tr = TextFieldBuilder.create()
   .prefColumnCount(4)
   .text("0.0")
   .onAction(new EventHandler<ActionEvent>() {
    @Override public void handle(ActionEvent e) {
     double angle = Math.toRadians(
      Double.parseDouble(
       ((TextField)e.getSource()).getText()));
     rotation_angle.set(angle%360.0);
     rotation(angle);
    }
   })
   .build();

  for(int i = 0; i < 3; i++ ) {
   c[i] = CircleBuilder.create()
    .radius(0.0).fill(color[i]).build();

   l[i] = LineBuilder.create()
    .strokeWidth(0.0).stroke(Color.BLACK).build();

   org_x[i] = new SimpleDoubleProperty(ORIGIN_X);
   org_y[i] = new SimpleDoubleProperty(ORIGIN_Y);
   map_x[i] = org_x[i].subtract(ORIGIN_X).divide(PPC);
   map_y[i] = org_y[i].negate().add(ORIGIN_Y).divide(PPC);
   c[i].radiusProperty().bind(
       Bindings.multiply(slider.valueProperty(), 10.0));

   l[i].strokeWidthProperty().bind(
       Bindings.multiply(slider.valueProperty(), 5.0));

   tx[i] = TextFieldBuilder.create()
    .prefColumnCount(2).build();

   ty[i] = TextFieldBuilder.create()
    .prefColumnCount(2)build();
  }    

  for (int i = 0; i < a_x_l.length; i++) {
   double delta_y = (INNER_HEIGHT - 5) - (i+1) * (PPC);
   a_x_l[i] = new Line(
       MARGIN, delta_y, MARGIN + INNER_WIDTH, delta_y);
   a_x_l[i].setStrokeWidth(
          (i+1)%5==0 ? THICK_STROKE : THIN_STROKE);
  }

  for (int i = 0; i < a_y_l.length; i++) {
   double delta_x = (MARGIN + 5) + (i+1) * (PPC);
   a_y_l[i] = new Line(
      delta_x, MARGIN, delta_x, MARGIN + INNER_HEIGHT);
   a_y_l[i].setStrokeWidth(
          (i+1)%5==0 ? THICK_STROKE : THIN_STROKE);
  }

  gp_x = org_x[0].add(org_x[1]).add(org_x[2]).divide(3.0);
  gp_y = org_y[0].add(org_y[1]).add(org_y[2]).divide(3.0);

  gp_c.centerXProperty().bind(gp_x);
  gp_c.centerYProperty().bind(gp_y);
  gp_c.radiusProperty().bind(
        Bindings.min(slider.valueProperty(), 2.0));
 }

 StringConverter<Number> sc_x =
  new StringConverter<Number>() {
   @Override public Number fromString(String from) {
    double map_x = Double.parseDouble(from);
    double org_x = (MARGIN + 5) + (map_x*PPC);
    return new Double(org_x);
   }

   @Override public String toString(Number org_x) {
    double map_x = (org_x.doubleValue() - (MARGIN + 5))/PPC;
    return String.format("%2.1f", map_x);
   }
 };

 StringConverter<Number> sc_y =
  new StringConverter<Number>() {
   @Override public Number fromString(String from) {
    double map_y = Double.parseDouble(from);
    double org_y = (INNER_HEIGHT - 5) - (map_y*PPC);
    return new Double(org_y);
   }

   @Override public String toString(Number org_y) {
    double map_y = ((INNER_HEIGHT - 5) - org_y.doubleValue())/PPC;
    return String.format("%2.1f", map_y);
   }
 };

 StringConverter<Number> sc_angle =
  new StringConverter<Number>() {
   @Override public Number fromString(String from) {
    double angle = Double.parseDouble(from);
    double r_angle = Math.toRadians(angle%360.0);
    return new Double(r_angle);
   }

   @Override public String toString(Number r_angle) {
    double angle = Math.toDegrees(r_angle.doubleValue());
    return String.format("%2.1f", (angle%360.0));
   }
 };

 public MultiTouch1() {
  initialize();
    
  for(int i = 0; i < 3; i++) {
   int j = (i == 2) ? 0 : i+1;

   l[i].startXProperty().bind(c[i].centerXProperty());
   l[i].startYProperty().bind(c[i].centerYProperty());
   l[i].endXProperty().bind(c[j].centerXProperty());
   l[i].endYProperty().bind(c[j].centerYProperty());

   Bindings.bindBidirectional(
            c[i].centerXProperty(), org_x[i]);
   Bindings.bindBidirectional(
            c[i].centerYProperty(), org_y[i]);
   Bindings.bindBidirectional(
        tx[i].textProperty(), c[i].centerXProperty(), sc_x);
   Bindings.bindBidirectional(
        ty[i].textProperty(), c[i].centerYProperty(), sc_y);
   Bindings.bindBidirectional(
        tr.textProperty(), rotation_angle, sc_angle);
  }

  NumberBinding tmp_area =
   map_x[0].multiply(map_y[1])
    .add(map_x[1].multiply(map_y[2]))
    .add(map_x[2].multiply(map_y[0]))
    .subtract(map_x[0].multiply(map_y[2]))
    .subtract(map_x[1].multiply(map_y[0]))
    .subtract(map_x[2].multiply(map_y[1]))
    .divide(2.0);

  area = new When(tmp_area.lessThan(0))
       .then(tmp_area.negate())
       .otherwise(tmp_area);
    
  area_Label.textProperty()
   .bind(new SimpleStringProperty("Area = ")
    .concat(area.asString("%2.1f")));
 }

 private void initialize() {
  clickcount = 0;
 }

 @Override public void start(Stage stage) {
  final Group root;    
  Scene scene = SceneBuilder.create()
   .width(WIDTH).height(HEIGHT)
   .fill(Color.WHITE)
   .root(root = GroupBuilder.create()
   .children(rectangle)
   .build()
  ).build();
   
  root.getChildren().addAll(a_x_l);
  root.getChildren().addAll(a_y_l);
    
  root.getChildren().addAll(
   LineBuilder.create()
    .startX(MARGIN).startY(ORIGIN_Y)
    .endX(MARGIN + INNER_WIDTH).endY(ORIGIN_Y)
    .build(),
   LineBuilder.create()
    .startX(ORIGIN_X).startY(MARGIN)
    .endX(ORIGIN_X).endY(MARGIN + INNER_HEIGHT)
    .build()
  );

  root.getChildren().addAll(
   HBoxBuilder.create()
    .spacing(3).padding(new Insets(10, 10, 10, 10))
    .layoutX(10)
    .alignment(Pos.BOTTOM_CENTER)
    .children(
     LabelBuilder.create().text("A(").build(),
     tx[0],
     LabelBuilder.create().text(",").build(),
     ty[0],
     LabelBuilder.create().text("),").build(),
     LabelBuilder.create().text("B(").build(),
     tx[1],
     LabelBuilder.create().text(",").build(),
     ty[1],
     LabelBuilder.create().text("),").build(),
     LabelBuilder.create().text("C(").build(),
     tx[2],
     LabelBuilder.create().text(",").build(),
     ty[2],
     LabelBuilder.create().text(") ⇒ ").build(),
     area_Label
    )
    .build(),
   HBoxBuilder.create()
    .layoutX(INNER_WIDTH - 270).layoutY(INNER_HEIGHT - 30)
    .children(
     LabelBuilder.create().text("Rotate").build(),
     tr,
     LabelBuilder.create().text("Zoome").build(),
     slider
    )
    .build()
   );

  rectangle.setOnMouseClicked(new EventHandler<MouseEvent>() {
   @Override public void handle(MouseEvent e) {
    if(e.isSynthesized()) { e.consume(); return; }
    if (clickcount > 2) { initialize(); }

    final int i = clickcount;
    final double x = e.getSceneX();
    final double y = e.getSceneY();

    Task<Void> task = new Task<Void>() {
     @Override public Void call() {
      Platform.runLater(new Runnable() {
       @Override public void run() {
        if(i == 0) {
         root.getChildren()
           .removeAll(l[0], l[1], l[2], c[0], c[1], c[2], gp_c);
        }
        org_x[i].set(x);
        org_y[i].set(y);
        switch(i) {
         case 0:
          root.getChildren().add(c[0]);
          break;
         case 1:
          root.getChildren().remove(c[0]);
          root.getChildren().addAll(l[0], c[0], c[1]);
          break;
         case 2:
          root.getChildren().removeAll(c[0], c[1], l[0]);
          root.getChildren()
            .addAll(l[0], l[1], l[2], c[0], c[1], c[2], gp_c);
          break;
         default :
        }
       }
      });
      return null;
     }
    };
    new Thread(task).start();
    clickcount++;
    e.consume();
   }
  });

  rectangle.setOnTouchPressed(new EventHandler<TouchEvent>() {
   @Override public void handle(TouchEvent e) {
    int tc = e.getTouchCount();
    if(tc != 3) { e.consume(); return; }
    final List<TouchPoint> tps = e.getTouchPoints();
    Platform.runLater(new Runnable() {
     @Override public void run() {
      root.getChildren().removeAll(
          l[0], l[1], l[2], c[0], c[1], c[2], gp_c);
      int i = 0;
      for(TouchPoint p : tps) {
       org_x[i].set(p.getSceneX());
       org_y[i].set(p.getSceneY());
       i++;
      }
      root.getChildren().addAll(
          l[0], l[1], l[2], c[0], c[1], c[2], gp_c);
     }
    });
    clickcount += tc;
    e.consume();
   }
  });

  rectangle.setOnZoom(new EventHandler<ZoomEvent> (){
   @Override public void handle(ZoomEvent e) {
    double scale = e.getZoomFactor()
             * slider.valueProperty().get();
    slider.valueProperty().set(scale);
    e.consume();
   }
  });

  rectangle.setOnRotate(new EventHandler<RotateEvent>() {
   @Override public void handle(RotateEvent e) {
    double angle = e.getAngle()/20.0;
    rotation_angle.set(e.getTotalAngle()%360.0);
    rotation(angle);
    e.consume();
   }
  });

  stage.setTitle("Triangle Area");
  stage.setScene(scene);
  stage.show();
 }

 private void rotation(double angle) {
  double o_x[] = new double[3];
  double o_y[] = new double[3];
  double r_x[] = new double[3];
  double r_y[] = new double[3];
  double g_x = gp_x.doubleValue();
  double g_y = gp_y.doubleValue();
  for(int i = 0; i < 3; i++) {
   o_x[i] = org_x[i].get();
   o_y[i] = org_y[i].get();
   r_x[i] = (o_x[i]-g_x)*Math.cos(angle
        - (o_y[i]-g_y)*Math.sin(angle) + g_x;
   r_y[i] = (o_x[i]-g_x)*Math.sin(angle)
        + (o_y[i]-g_y)*Math.cos(angle) + g_y;
  }
  for(int i = 0; i < 3; i++) {
   org_x[i].set(r_x[i]);
   org_y[i].set(r_y[i]);
  }
 }

 public static void main(String[] args) {
  launch(args);
 }
}


一連の JavaFX のお試しネタは、これで、ひとまずお終い。

なお、このソースを実行すると、ウィンドウのサイズは変更できるけど、それに合わせて座標系の範囲が拡大するということまではしてません。
記事へブログ気持玉 / トラックバック / コメント


JavaFX の Bind で三角形の面積を求める... の続き(3)

2012/12/28 02:25
If you prefer to read this article in ENGLISH, go to [in English] page.

JavaFX の Bind で三角形の面積を求める... の続き(2)の続き ...

座標位置を表示させるのに TextField を使っているのだから、そこから値を入力できるようにして、かつ、入力した値の座標位置を頂点にすることを考えてみる。

今のコードは、頂点を表示している点(Circle)の centerX と centerY との値を Textfield の text に(片方向の)バインドをしているのだが、これを双方向にバインドすればよさそうだ。

で、やってみた。

今回は、全部のソースを書き出すのが面倒なと、また、長くなるので、差分の断片だけを示す。

まず import 文を 2 つ追加する。

import javafx.util.StringConverter;
import javafx.beans.binding.Bindings;


そして、インスタンスイニシャライザとコンストラクタとの間に、x 軸用と y 軸用の StringCenverter を 2 つ作る。

これらは、TextField で入力される値を文字列として保持する text プロパティと、頂点を表現する double プロパティとの間の型変換を行うためのコンバータである。
単に、型変換であればコンバータは一つで済むが、今回は、GUI 上に表現している座標系の値(TextField に表示される、あるいは入力される値)と、Circle や Line を Scene 上に描画する位置を表現する座標系の値とのマッピングも行いたいので x 軸用と y軸用の 2 つのコンバータが必要になる。

コンバータクラスには 2 つのメソッドを用意する。

1つは fromString メソッドで、ここに Textfield で入力された座標点の値(文字列)から、描画位置の pixel 位置の値(double)との間の変換処理を書く。もう 1 つは toString メソッドで、こちらは pixel 位置の値(double)を、
Textfield に表示する座標点の値(文字列)に変換する処理を書く。

で、実装したコードがこれ。

  StringConverter<Number> sc_x = new StringConverter<Number>() {
    @Override public Number fromString(String from) {
      double map_x = Double.parseDouble(from);
      double org_x = 10 + (map_x*96/2.54);
      return new Double(org_x);
    }
    @Override public String toString(Number org_x) {
      double map_x = (org_x.doubleValue()-10)*2.54/96;
      return String.format("%2.1f", map_x);
    }
  };

  StringConverter<Number> sc_y = new StringConverter<Number>() {
    @Override public Number fromString(String from) {
      double map_y = Double.parseDouble(from);
      double org_y = 490 - (map_y*96/2.54);
      return new Double(org_y);
    }
    @Override public String toString(Number org_y) {
      double map_y = (490 - org_y.doubleValue())*2.54/96;
      return String.format("%2.1f", map_y);
    }
  };

  public TriangleArea2() { // クラス名 TriangleArea2 に変更
    initialize();

    for(int i = 0; i < 3; i++) {
      int j = (i == 2) ? 0 : i+1;

      l[i].startXProperty().bind(c[i].centerXProperty());
      l[i].startYProperty().bind(c[i].centerYProperty());
      l[i].endXProperty().bind(c[j].centerXProperty());
      l[i].endYProperty().bind(c[j].centerYProperty());

      // 双方向バインド
      // (クリックした位置の座標と点の中心の座標とを双方向バインド)
      Bindings.bindBidirectional(c[i].centerXProperty(), org_x[i]);
      Bindings.bindBidirectional(c[i].centerYProperty(), org_y[i]);

      // 双方向バインド
      // (点の中心座標とTextfieldのtextとを双方向バインド - 変換あり)
      Bindings.bindBidirectional(
        tx[i].textProperty(), c[i].centerXProperty(), sc_x);
      Bindings.bindBidirectional(
        ty[i].textProperty(), c[i].centerYProperty(), sc_y);
    }

    // ...


頂点座標を数値でもクリックでも入力できる


図中の黄色い丸で示した箇所などで、座標位置の数値を入力すると、その位置が頂点になる。もちろん、頂点を示す点(円)の位置が変化し、同時に辺の位置も頂点の位置に追従して変化する。

目盛りの交点をクリックしたつもりでも、その交点座標からすこしずれた位置をクリックしてしまうことはよくあるが、そんな時に、この実装により、座標位置を数値指定で入力できるので、調整・補正が容易になる。


さぁ、次に何ができるか。

で思いついたのが、3 点同時タッチで三角形を指定すること。
マルチタッチのイベントで処理すればよさそうだ。

つづく ...
記事へブログ気持玉 / トラックバック / コメント


JavaFX の Bind で三角形の面積を求める... の続き(2)

2012/12/28 02:07
If you prefer to read this article in ENGLISH, go to [in English] page.

JavaFX の Bind で三角形の面積を求める... の続き(1)の続き ...

入力だけでなく、出力も GUI を使ってみよう。

具体的には、
- X-Y軸の座標平面の第一象限にマップする(座標原点を左上から左下にする)
- 単位を cm にする(メモリの補助線を入れる)
- 3頂点の座標と面積の値を画面上部に表示する(表示桁も最小限に)
ということを考えた。

原点O は、左下から座標平面までのマージン 5 pixie とり、さらに x軸, y軸とも 5pixie の offset を取った位置とする。また、96ピクセル/インチを前提にしてピクセル単位から cm 単位に変換する。x軸, y軸のメモリ用補助線も表示するとしよう(細い線で)。さらに、画面上に表示する座標位置の書式は "%2.1f" という書式で表示する。小数点以下の表示桁を多くしても、表示する領域の限られていることだし。

これらを実装すると、次のようソースコードになった。

import javafx.application.Application;
import javafx.beans.binding.NumberBinding;
import javafx.beans.binding.When;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.GroupBuilder;
import javafx.scene.Scene;
import javafx.scene.SceneBuilder;
import javafx.scene.control.Label;
import javafx.scene.control.LabelBuilder;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFieldBuilder;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBoxBuilder;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.CircleBuilder;
import javafx.scene.shape.Line;
import javafx.scene.shape.LineBuilder;
import javafx.scene.shape.RectangleBuilder;
import javafx.stage.Stage;

public class TriangleArea1 extends Application {
  private int clickcount = 0;
  private Circle c[] = new Circle[3];
  private Line l[] = new Line[3];
  private Line a_x_l[] = new Line[12];
  private Line a_y_l[] = new Line[12];
  private TextField tx[] = new TextField[3];
  private TextField ty[] = new TextField[3];
  private Label area_Label = new Label();

  private DoubleProperty org_x[] = new SimpleDoubleProperty[3];
  private DoubleProperty org_y[] = new SimpleDoubleProperty[3];
  private NumberBinding map_x[] = new NumberBinding[3];
  private NumberBinding map_y[] = new NumberBinding[3];
  private NumberBinding area;

  {
    for(int i = 0; i < 3; i++ ) {
      // 頂点を表現する点(Circle)と
      // 辺を表現する線(Line)を用意
      c[i] = CircleBuilder.create()
                          .radius(3.0)
                          .fill(Color.RED)
                          .build();
      l[i] = LineBuilder.create()
                          .strokeWidth(2.0)
                          .stroke(Color.RED)
                          .build();

      // 頂点の座標位置を表示する TextField を用意
      tx[i] = TextFieldBuilder.create()
                              .prefColumnCount(2)
                              .build();
      ty[i] = TextFieldBuilder.create()
                              .prefColumnCount(2)
                              .build();

      // クリックした位置をプロパティとして保持
      // (初期値は原点Oの位置のpixel値)
      org_x[i] = new SimpleDoubleProperty(10);
      org_y[i] = new SimpleDoubleProperty(490);

      // 画面に表示した座標系での座標位置
      // (クリックした位置のプロパティがバインドされ計算)
      map_x[i] = org_x[i].subtract(10)
                         .multiply(2.54)
                         .divide(96.0);
      map_y[i] = org_y[i].negate()
                         .add(490)
                         .multiply(2.54)
                         .divide(96.0);
    }

    // x軸、y軸の補助線を用意
    for (int i = 0; i < 12; i++) {
      double delta_y = 490.0 - (i+1) * (96.0 / 2.54);
      double delta_x = 10.0 + (i+1) * (96.0 / 2.54);
      a_x_l[i] = new Line(5, delta_y, 495, delta_y);
      a_x_l[i].setStrokeWidth((i+1)%5==0 ? 0.3 : 0.1);
      a_y_l[i] = new Line(delta_x, 5, delta_x, 495);
      a_y_l[i].setStrokeWidth((i+1)%5==0 ? 0.3 : 0.1);
    }
  }

  public TriangleArea1() {
    initialize();

    for(int i = 0; i < 3; i++) {
      int j = (i == 2) ? 0 : i+1;

      // 辺の始点終点に頂点の中心をバインド
      l[i].startXProperty().bind(c[i].centerXProperty());
      l[i].startYProperty().bind(c[i].centerYProperty());
      l[i].endXProperty().bind(c[j].centerXProperty());
      l[i].endYProperty().bind(c[j].centerYProperty());

      // 頂点を表現する点の中心にクリックした位置をバインド
      c[i].centerXProperty().bind(org_x[i]);
      c[i].centerYProperty().bind(org_y[i]);

      // 座標位置表示用 TextField の Text に
      // 座標位置の値の文字列表現をバインド
      tx[i].textProperty().bind(map_x[i].asString("%2.1f"));
      ty[i].textProperty().bind(map_y[i].asString("%2.1f"));
    }

    // 面積を3つの頂点の座標位置がバインドされ計算
    NumberBinding tmp_area =
             map_x[0].multiply(map_y[1])
        .add(map_x[1].multiply(map_y[2]))
        .add(map_x[2].multiply(map_y[0]))
        .subtract(map_x[0].multiply(map_y[2]))
        .subtract(map_x[1].multiply(map_y[0]))
        .subtract(map_x[2].multiply(map_y[1]))
        .divide(2.0);

    // 絶対値をとる計算を When().then().otherwise() で実装
    area = new When(tmp_area.lessThan(0))
              .then(tmp_area.negate())
              .otherwise(tmp_area);

    // 面積の値を表示するラベルの text を、
    // 計算された面積の値の文字列表現を使うようにバインド
    area_Label.textProperty().bind(
      new SimpleStringProperty("Area = ")
              .concat(area.asString("%2.1f")));
  }

  private void initialize() {
    clickcount = 0;
  }

  @Override public void start(Stage stage) {
    final Group root;
    Scene scene = SceneBuilder.create()
      .width(500).height(500)
      .fill(Color.WHITE)
      .root(root = GroupBuilder.create()
        .children(RectangleBuilder.create()
          .layoutX(5).layoutY(5)
          .width(490).height(490)
          .fill(Color.LIGHTSKYBLUE)
          .onMouseClicked(new EventHandler<MouseEvent>() {
            // クリック時のイベントハンドラ
            @Override public void handle(MouseEvent e) {
              if (clickcount > 2) { initialize(); }
              for (int j = clickcount; j < 3; j++) {
                org_x[j].set(e.getSceneX());
                org_y[j].set(e.getSceneY());
              }
              clickcount++;
            }
          }).build(),
        // 原点O を通る x 軸
        LineBuilder.create()
          .startX(5).startY(490)
          .endX(495).endY(490)
          .build(),
        // 原点O を通る y 軸
        LineBuilder.create()
          .startX(10).startY(5)
          .endX(10).endY(495)
          .build(),
        // x 軸の補助線(1cm刻み)
        a_x_l[0], a_x_l[1], a_x_l[2], a_x_l[3],
        a_x_l[4], a_x_l[5], a_x_l[6], a_x_l[7],
        a_x_l[8], a_x_l[9], a_x_l[10], a_x_l[11],
        // y 軸の補助線(1cm刻み)
        a_y_l[0], a_y_l[1], a_y_l[2], a_y_l[3],
        a_y_l[4], a_y_l[5], a_y_l[6], a_y_l[7],
        a_y_l[8], a_y_l[9], a_y_l[10], a_y_l[11],
        // 三角形の辺
        l[0], l[1], l[2],
        // 三角形の頂点
        c[0], c[1], c[2],
        // 座標位置及び面積の表示
        HBoxBuilder.create()
          .layoutX(10)
          .padding(new Insets(10, 10, 10, 10)).spacing(3)
          .alignment(Pos.BOTTOM_CENTER)
          .children(
            LabelBuilder.create().text("A(").build(),
            tx[0],
            LabelBuilder.create().text(",").build(),
            ty[0],
            LabelBuilder.create().text("),").build(),
            LabelBuilder.create().text("B(").build(),
            tx[1],
             LabelBuilder.create().text(",").build(),
            ty[1],
            LabelBuilder.create().text("),").build(),
            LabelBuilder.create().text("C(").build(),
            tx[2],
            LabelBuilder.create().text(",").build(),
            ty[2],
            LabelBuilder.create().text(") ⇒ ").build(),
            area_Label
          ).build()
      ).build()
    ).build();

    stage.setTitle("Triangle Area");
    stage.setScene(scene);
    stage.show();
  }

  public static void main(String[] args) {
    launch(args);
  }
}


頂点座標と面積を画面上に表示


いやいや長い ...
Builderクラスで Node を build しているのもあるけど、画面表示を凝るとこうなってしまうのかもしれない。FXML を使えばいいじゃんという声が聞こえてきそう。

これで、結果の表示も GUI 上に表現できるようにはなった。

せっかく、座標位置を TextField を使っているのだから、マウスクリックで座標を入力するだけでなく、TextField に直接、値を入力できるようにさせてみよう。

つづく ...
記事へブログ気持玉 / トラックバック / コメント


JavaFX の Bind で三角形の面積を求める... の続き(1)

2012/12/28 01:42
If you prefer to read this article in ENGLISH, go to [in English] page.


Pro JavaFX 2 に、Binding の例として三角形の面積を求める例があった。

3つの頂点(x1, y1), (x2, y2), (x3, y3) の座標を与えると、次のような公式に当てはめて面積が求まるというもの。

面積 = (x1*y2 + x2*y3 + x3*y1 - x1*y3 - x2*y1 - x3*y2)/2

ソースコードは、こんな感じ。


public class TriangleAreaFluentExample {
  public static void main(String[] args) {
    IntegerProperty x1 = new SimpleIntegerProperty(0);
    IntegerProperty y1 = new SimpleIntegerProperty(0);
    IntegerProperty x2 = new SimpleIntegerProperty(0);
    IntegerProperty y2 = new SimpleIntegerProperty(0);
    IntegerProperty x3 = new SimpleIntegerProperty(0);
    IntegerProperty y3 = new SimpleIntegerProperty(0);

    final NumberBinding area =
                      x1.multiply(y2)
                        .add(x2.multiply(y3))
                        .add(x3.multiply(y1))
                        .subtract(x1.multiply(y3))
                        .subtract(x2.multiply(y1))
                        .subtract(x3.multiply(y2))
                        .divide(2.0D);

    StringExpression output = Bindings.format(
      "3点 A(%d,%d), B(%d,%d), C(%d,%d) " +
            "の三角形の面積は %3.1f",
                  x1, y1, x2, y2, x3, y3, area);

    x1.set(0); y1.set(0);
    x2.set(6); y2.set(0);
    x3.set(4); y3.set(3);
    System.out.println(output.get());

    x1.set(1); y1.set(0);
    x2.set(2); y2.set(2);
    x3.set(0); y3.set(1);
    System.out.println(output.get());

    x1.set(10); y1.set(10);
    x2.set(2); y2.set(2);
    x3.set(5); y3.set(1);
    System.out.println(output.get());
  }
}

コンソールに実行結果が出力されている



これを一目見て、JavaFX を使っていながら GUI がないというのも面白いと思った。
だけど、やはり JavaFX なのだから GUI をつけてみたくなった。

そこで、おそらく、誰でも思いつくであろうアレンジだと思うけど、
マウスクリックを 3 箇所行って、それらを 3 つの頂点としてみなして面積を求めるというものを考えた。
見た目も、わかりやすくするために、クリックした箇所に Circle を表示し、Circle 間を Line で結線することしてみよう。

まず、クリックした 3 点の座標 (x1, y1), (x2, y2), (x3, y3) を、それぞれプロパティに持つ。
それらのプロパティで表現される頂点を表現する Circle の centerX と centerY にバインドする。
もちろん、頂点は 3 つなので、Circle も 3 つある。
さらに 2 つの Circle 間を結線する Line のstartX, startY, endX, endY に 2 つの Circle の centerX と centerY を、それぞれバインドする。

面積は、クリックした点 (X, Y) のピクセル単位の値のまま計算する。
そして、絶対値で求めるために When().then().otherwise() という興味深い API を使う。

ということで作ったソースがこれ。


import javafx.application.Application;
import javafx.beans.binding.NumberBinding;
import javafx.beans.binding.When;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.GroupBuilder;
import javafx.scene.Scene;
import javafx.scene.SceneBuilder;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.shape.LineBuilder;
import javafx.scene.shape.RectangleBuilder;
import javafx.stage.Stage;

public class TriangleArea0 extends Application {
  private int clickcount = 0;
  private Circle c[] = new Circle[3];
  private Line l[] = new Line[3];
  private DoubleProperty x[] = new SimpleDoubleProperty[3];
  private DoubleProperty y[] = new SimpleDoubleProperty[3];

  private NumberBinding area;

  {
    for(int i = 0; i < 3; i++ ) {
        c[i] = new Circle();
        l[i] = new Line();
        x[i] = new SimpleDoubleProperty(0);
        y[i] = new SimpleDoubleProperty(0);
    }
  }

  public TriangleArea0() {
    initialize();

    for(int i = 0; i < 3; i++) {
      int j = (i == 2) ? 0 : i+1;

      l[i].startXProperty().bind(c[i].centerXProperty());
      l[i].startYProperty().bind(c[i].centerYProperty());
      l[i].endXProperty().bind(c[j].centerXProperty());
      l[i].endYProperty().bind(c[j].centerYProperty());

      c[i].centerXProperty().bind(x[i]);
      c[i].centerYProperty().bind(y[i]);
    }

    NumberBinding
      tmp_area = x[0].multiply(y[1])
            .add(x[1].multiply(y[2]))
            .add(x[2].multiply(y[0]))
            .subtract(x[0].multiply(y[2]))
            .subtract(x[1].multiply(y[0]))
            .subtract(x[2].multiply(y[1]))
            .divide(2.0);

    area = new When(tmp_area.lessThan(0))
              .then(tmp_area.negate())
              .otherwise(tmp_area);
  }

  private void initialize() {
    clickcount = 0;
  }

  private void print_area() {
    System.out.println(
        "A = (" + x[0].get() + ", " + y[0].get() + ")");
    System.out.println(
        "B = (" + x[1].get() + ", " + y[1].get() + ")");
    System.out.println(
        "C = (" + x[2].get() + ", " + y[2].get() + ")");
    System.out.println("Area = " + area.doubleValue());
  }

  @Override public void start(Stage stage) {
    final Group root;
    Scene scene = SceneBuilder.create()
      .width(500).height(500)
      .fill(Color.WHITE)
      .root(root = GroupBuilder.create()
        .children(RectangleBuilder.create()
          .layoutX(5).layoutY(5)
          .width(490).height(490)
          .fill(Color.LIGHTSKYBLUE)
          .onMouseClicked(new EventHandler<MouseEvent>() {
            @Override public void handle(MouseEvent e) {
              if (clickcount > 2) { initialize(); }
              for (int j = clickcount; j < 3; j++) {
                x[j].set(e.getSceneX());
                y[j].set(e.getSceneY());
              }
              clickcount++;
              if (clickcount == 3) { print_area(); }
            }
        }).build(),
        LineBuilder.create()
          .startX(5).startY(490)
          .endX(495).endY(490)
          .build(),
        LineBuilder.create()
          .startX(10).startY(5)
          .endX(10).endY(495)
          .build(),
        l[0], l[1], l[2],
        c[0], c[1], c[2]
      ).build()
    ).build();

    stage.setTitle("Triangle Area");
    stage.setScene(scene);
    stage.show();
  }

  public static void main(String[] args) {
    launch(args);
  }
}
三角形の頂点をマウスクリックして与える



これを実行すると、最初のクリックで、その箇所に点が表示される。
2回目のクリックで、その箇所に点が表示され、かつ、最初の点との間が線で結ばれる。
3回目のクリックで、その箇所に点が表示され、残りの 2 辺も表示される。
そして、標準出力にクリックした 3 箇所の座標と、その 3 点を頂点とする三角形の面積が出力される。
この時、表示される値は座標はピクセル単位、面積は平方ピクセル単位である。

4回目のクリックは、1回目のクリックがなされたものとして処理している。

しかし、これだと入力だけが GUI なので、面白みも半分。そこで、出力も GUI を使おうと思う。


(1エントリの文字数制限のがあるみたいなので) つづく
記事へかわいい ブログ気持玉 1 / トラックバック 0 / コメント 0


画面のない JavaFX での Property の bind

2012/12/12 22:27
Pro JaxaFX 2 を読んでいたら、UI がなくても、Property, Property#bind, Binding が使えるサンプルがあった。

Chapter 3 の三角形の面積を求めるやつ。

なるほどねー

※ 下の英語表記は PC 版で表示でのみ効きます。スマホ表示では効きません。


[in English]



記事へブログ気持玉 / トラックバック / コメント


トップへ | みんなの「JavaFX」ブログ

Java etc... JavaFXのテーマ/BIGLOBEウェブリブログ