Wednesday, 11 December 2013

PatternLock control in JavaFX

It is great to see JavaFX applications working on Android. I was always fascinated to see how the JavaFX touch event handlers works. The recent post from Intel using JavaFX to implement multi-touch has given me much confidence to give a try with the JavaFX touch handlers. And here I ended up with a simple touch based PatternLock control.

To start with, below is the control how it looks with its default css styling. It works both for mouse event handlers and touch event handlers.


However, one can customize the complete look and feel with the css styling. Below are the screenshots of the control with different css styling.


About the control:

I hope there is no need to give an introduction about the PatternLock behavior, as it is quite familiar and widely used in various platforms. However I will quickly walkthrough about the basics/features that I have implemented in this control.

The control consists of dots rendered in a matrix pattern. Currently the control supports only 2X2, 3X3 and 4X4 matrix patterns, assuming that the bigger the pattern means the more complicated the generated code will be(which is not logical). This size is set by PatternLockControl.PatternSize static enum value. Each dot is surrounded with an active region. If the user touches/clicks on this region the pattern will be started.


Note : Once the control is rendered the pattern size cannot be changed.

User can create a code by swiping(for touch) or dragging(for mouse) between the dots. Every two dots are connected with a line and a small arrow is shown indicating the direction from which the line points to.


Whenever a drag/swipe enters the active region of another dot, the two dots are connected automatically and a new line is builded from the second dot. Likewise the user can join multiple dots to create a pattern.

Each dot holds an integer value which is the base for generating the code. The value starts from 0 and increments its value for each dot. For example, the below pattern generates the code “0158763”.


Note that each dot can be activated only once while building the pattern. So it is not possible to generate a code like “013415”. Eventually for a 2X2 matrix, the max code length that can be builded is 4 and likewise, for 3X3 it is 9 and for 4X4 it is 16.

Using the generated pattern code:

There is a setter method setOnPatternDetected() for the control which accepts a CallBack<String,Boolean> instance. The generated code is provided as parameter to the CallBack.. Developer needs to evaluate this code and return a Boolean value stating whether the pattern is correct or not. The pattern will be cleared/styled based on the return value.
So if the value returned is :
  • true : Clears the pattern.
  • false : Applies the error styling to the pattern.
  • null : Doesn't clear the pattern. However the previous pattern is cleared when the user starts for new pattern.
For a quick overview below are the screenshots for different returned Boolean values.



Thats it !! I know that the API for this control is pretty small. Suggestions or improvements are highly welcomed so as to make it more stable and usable. :)

The source code along with the demos can be found here :

To get a very quick idea about its behavior please check the below demos.

Using Touch:

Using Mouse:

Happy Coding !! :)

Monday, 5 August 2013

Magnifier in JavaFX 2



The content of this blog is moved and published here.

Please wait...The page will automatically redirect.










Wednesday, 18 July 2012

Gradients In JavaFX 2



The content of this blog is moved and published here.

Please wait...The page will automatically redirect.










Tuesday, 12 June 2012

Scroller control in JavaFX 2



The content of this blog is moved and published here.












Wednesday, 18 January 2012

Sliding in JavaFX (It’s all about clipping)


JavaFX animation package provides many amazing animation/transition effects. Let’s look into another effect, “Sliding” effect of node (relative to the parent node).  The term ‘relative’ refers to, like the node should hide relatively under the parent node by sliding. If you are familiar with “Titled Pane”, I am speaking about the same effect how the content of the pane is shown and hide. I am trying to achieve this with some simple logic and using some properties of a node.

Let’s look into an example, how we can hide/show our custom nodes by implementing this sliding effect. In brief, it is all about clipping our node for some duration of time. There are some amazing blogs/articles available in internet explaining the “clip” functionality of a node. I am going to use this clip feature, to achieve this effect.

Below are the screenshots of the example to which the sliding effect is implemented. On click of “Slide Down”, the top pane (blue color) content is shown and on click of “Slide Up” the top pane content is hidden.


Before going into the actual java code, let me explain the logic pictorially.  In the below image, the red color dotted box is the node that is to be shown/hidden by sliding.  The grey shaded region is the Rectangle that is used to clip the node (Means the visible part of node after clipping). And the black border box is the parent node.

 
To get this effect we need to handle three parameters (A, B & C as shown in picture)
                A – Start Point of the Node
                B – Start point of the clip rectangle
                C – Height of the clip rectangle.

Considering the above scenario the code to slide up (hide) the node(topPane) is as below.

Rectangle2D boxBounds = new Rectangle2D(100, 100, 250, 250);
Rectangle clipRect = new Rectangle();
clipRect.setWidth(boxBounds.getWidth());

StackPane topPane = new StackPane();
topPane.setPrefSize( boxBounds.getWidth(),boxBounds.getHeight());  topPane.setClip(clipRect);
                
/* Animation for scroll up. */
Timeline timelineUp = new Timeline();
timelineUp.setCycleCount(1); 
timelineUp.setAutoReverse(true);

final KeyValue kvUp1 = new KeyValue(clipRect.heightProperty(), 0);
final KeyValue kvUp2 = new KeyValue(clipRect.translateYProperty(), boxBounds.getHeight());
final KeyValue kvUp3 = new KeyValue(topPane.translateYProperty(), -boxBounds.getHeight());
final KeyFrame kfUp = new KeyFrame(Duration.millis(200), kvUp1, kvUp2, kvUp3);

timelineUp.getKeyFrames().add(kfUp);


In the above code, for the given amount of duration,
  • Clip rectangle heightProperty is decreased to 0,
  • translateYProperty of clip Rectangle is moved down to amount of height of the node and
  • translateYProperty of node is moved up to the amount of height of the node.

Similarly the code to slide down (show) the node (topPane) is as below

/* Animation for scroll down. */
Timeline timelineDown = new Timeline();
timelineDown.setCycleCount(1);
timelineDown.setAutoReverse(true);

final KeyValue kvDwn1 = new KeyValue(clipRect.heightProperty(), boxBounds.getHeight());
final KeyValue kvDwn2 = new KeyValue(clipRect.translateYProperty(), 0);
final KeyValue kvDwn3 = new KeyValue(topPane.translateYProperty(), 0);
final KeyFrame kfDwn = new KeyFrame(Duration.millis(200), kvDwn1, kvDwn2, kvDwn3);

timelineDown.getKeyFrames().add(kfDwn);

In the above code, for the given amount of duration,
  • Clip rectangle heightProperty is to the height of the node, 
  • translateYProperty of clip Rectangle is moved up to 0 and 
  • translateYProperty of node is moved down to 0. 

Combining all the logic together, below is the final SSCCE (Short, Self Contained, Correct Example) code of the above example.
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.geometry.Rectangle2D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.RectangleBuilder;
import javafx.scene.text.Font;
import javafx.scene.text.TextBuilder;
import javafx.stage.Stage;
import javafx.util.Duration;

public class SlideEffectDemo extends Application {

 private Rectangle2D boxBounds = new Rectangle2D(100, 100, 250, 200);
 
 private StackPane bottomPane;
 private StackPane topPane;
 private Rectangle clipRect;
 private Timeline timelineUp;
 private Timeline timelineDown;
 
 public static void main(String[] args) {
  Application.launch(args);
 }
 
 @Override
 public void start(Stage stage) throws Exception {
  VBox root = new VBox();
  root.setAlignment(Pos.CENTER);
  root.autosize();
  Scene scene = new Scene(root);
  stage.setTitle("Slide Effect Demo");
  stage.setWidth(350);
     stage.setHeight(300);
     stage.setScene(scene);
     stage.show();
     
  configureBox(root);
 }

 private void configureBox(VBox root) {
  StackPane container = new StackPane();
  container.setPrefHeight(250);
  container.setPrefSize(boxBounds.getWidth(), boxBounds.getHeight());
  container.setStyle("-fx-border-width:1px;-fx-border-style:solid;-fx-border-color:#999999;");
  
  // BOTTOM PANE 
  Stop[] stops = new Stop[] { new Stop(0, Color.web("#F89C8C")), new Stop(1, Color.web("#BE250A"))};
  LinearGradient lg = new LinearGradient(0, 0, 0, 1, true, CycleMethod.NO_CYCLE, stops);
  bottomPane = new StackPane();
  bottomPane.getChildren().addAll(RectangleBuilder.create().width(boxBounds.getWidth()).height(boxBounds.getHeight()).fill(lg).build(), 
     TextBuilder.create().text("Click the above \"Slide Down\" button to see the top pane content...").wrappingWidth(200).font(Font.font("Arial", 22)).build());
  
  // TOP PANE 
  Stop[] stops2 = new Stop[] { new Stop(0, Color.web("#FFFFFF")), new Stop(1, Color.web("#50AABC"))};
  LinearGradient lg2 = new LinearGradient(0, 0, 0, 1, true, CycleMethod.NO_CYCLE, stops2);
  StackPane sp1 = new StackPane();
  sp1.getChildren().add(TextBuilder.create().text("Click the below \"Slide Up\" button to see the bottom pane content...").wrappingWidth(200).font(Font.font("Arial", 22)).build());
  
  topPane = new StackPane();
  topPane.getChildren().addAll(RectangleBuilder.create().width(boxBounds.getWidth()).height(boxBounds.getHeight()).fill(lg2).build(), sp1);
  container.getChildren().addAll(bottomPane,topPane);
  
  setAnimation();
  
  Group gp = new Group();
  gp.getChildren().add(container);
  root.getChildren().addAll(getActionPane(),gp);
 }

 private void setAnimation(){
  // Initially hiding the Top Pane
  clipRect = new Rectangle();
        clipRect.setWidth(boxBounds.getWidth());
  clipRect.setHeight(0);
  clipRect.translateYProperty().set(boxBounds.getHeight());
  topPane.setClip(clipRect);
  topPane.translateYProperty().set(-boxBounds.getHeight());
  
  // Animation for bouncing effect.
  final Timeline timelineBounce = new Timeline();
  timelineBounce.setCycleCount(2);
  timelineBounce.setAutoReverse(true);
  final KeyValue kv1 = new KeyValue(clipRect.heightProperty(), (boxBounds.getHeight()-15));
  final KeyValue kv2 = new KeyValue(clipRect.translateYProperty(), 15);
  final KeyValue kv3 = new KeyValue(topPane.translateYProperty(), -15);
  final KeyFrame kf1 = new KeyFrame(Duration.millis(100), kv1, kv2, kv3);
  timelineBounce.getKeyFrames().add(kf1);
  
  // Event handler to call bouncing effect after the scroll down is finished.
  EventHandler onFinished = new EventHandler() {
            public void handle(ActionEvent t) {
             timelineBounce.play();
            }
        };
        
        timelineDown = new Timeline();
        timelineUp = new Timeline();
        
        // Animation for scroll down.
  timelineDown.setCycleCount(1);
  timelineDown.setAutoReverse(true);
  final KeyValue kvDwn1 = new KeyValue(clipRect.heightProperty(), boxBounds.getHeight());
  final KeyValue kvDwn2 = new KeyValue(clipRect.translateYProperty(), 0);
  final KeyValue kvDwn3 = new KeyValue(topPane.translateYProperty(), 0);
  final KeyFrame kfDwn = new KeyFrame(Duration.millis(200), onFinished, kvDwn1, kvDwn2, kvDwn3);
  timelineDown.getKeyFrames().add(kfDwn);
  
  // Animation for scroll up.
  timelineUp.setCycleCount(1); 
  timelineUp.setAutoReverse(true);
  final KeyValue kvUp1 = new KeyValue(clipRect.heightProperty(), 0);
  final KeyValue kvUp2 = new KeyValue(clipRect.translateYProperty(), boxBounds.getHeight());
  final KeyValue kvUp3 = new KeyValue(topPane.translateYProperty(), -boxBounds.getHeight());
  final KeyFrame kfUp = new KeyFrame(Duration.millis(200), kvUp1, kvUp2, kvUp3);
  timelineUp.getKeyFrames().add(kfUp);
 }
 
 private HBox getActionPane(){
  HBox hb = new HBox();
  hb.setAlignment(Pos.CENTER);
  hb.setSpacing(10);
  hb.setPrefHeight(40);
  Button upBtn = new Button("Slide Up");
  upBtn.setOnAction(new EventHandler() {
   @Override
   public void handle(ActionEvent arg0) {
    timelineUp.play();
   }
  });
  Button downBtn = new Button("Slide Down");
  downBtn.setOnAction(new EventHandler() {
   @Override
   public void handle(ActionEvent arg0) {
    timelineDown.play();
   }
  });
  hb.getChildren().addAll(downBtn,upBtn);
  return hb;
 }
}
If you observe in the above code, an extra timelineBounce is added to show the bounce effect when the top pane slides down.

A more realistic example of this slide effect can be found here.
Below are the screenshots of this example. The search form slides downwards and upwards on click of the “Search” label.


I hope you can relate the similar logic to slide the node towards LEFT and RIGHT, by using the widthProperty and translateXProperty of the node.

Happy Coding!!

Thursday, 5 January 2012

Percent Width for TableColumn in JavaFX 2.x TableView

The moment I started working with TableView in JavaFX 2.x, the first question raised in my mind is “Why there is no feature of setting the column widths in percentage as we do in HTML?” I am not sure what could be the reasons for not setting this feature. But if my application demands, I have to implement this by somehow ;). Ofcourse there are a couple of issues logged in JIRA  related to this functionality, but till it get resolved here is the way (workaround) how I tried to get it.

And another thing is with the display of “Extra Column” in TableView. I still remember, in the discussion forum, one guy has referred this term as “Ghost Column” :). Below is the screen shot of a simple TableView, with some setting of prefWidth(here 150px) to TableColumns and resize the screen.



Ok, let me get into some action...  The target is, on resizing the window the table columns should keep their relative sizes with respect to container/window.

For achieving this I am taking the help of GridPane for which percentage widths can be set to its columns through ColumnConstraints.

As a first step, a CustomTableColumn class is created that extends TableColumn, to hold the custom percentWidth property for the column.
The code for the CustomTableColumn is as below:

The idea is that, we will create a container, probably a StackPane (CustomTableView) which holds a GridPane and a TableView.  The number of columns in the GridPane will be synchronized with the number of columns in the TableView.

 StackPane is used as the content in each column of the grid. (StackPane because, it fits automatically to its parent size)

Now the core logic is,
Step 1 : We will be creating ColumnConstraints with the percentWidth that is specified for each column(CustomTableColumn) of the TableView. And set these ColumnConstraints to the grid.
Step 2 : The next step is binding each StackPane’s  widthProperty to its corresponding table column’s prefWidthProperty

Considering the above logic, the CustomTableView code is as below:


Combining all the above code, the below is the final SSCCE(Short, Self Contained, Correct Example) code:

On resizing the window, the output is as below. Not only the columns have maintained their relative sizes , but the extra column is no more visible. :)


I hope the above logic will serve the need till the auto sizing feature of TableView is implemented and released.

Happy Coding!! :)

Note: The above logic/code may be or can be improvised. Let me also know the changes for better implementation. :)