diff --git a/subprojects/jfoenix/jfoenix.gradle b/jfoenix/build.gradle similarity index 95% rename from subprojects/jfoenix/jfoenix.gradle rename to jfoenix/build.gradle index faa20087..994ae2b0 100644 --- a/subprojects/jfoenix/jfoenix.gradle +++ b/jfoenix/build.gradle @@ -31,9 +31,6 @@ task sourcesJar(type: Jar) { from sourceSets.main.allSource } -sourceSets.main.java.srcDirs = [rootProject.file('src')] -sourceSets.main.resources.srcDirs = [rootProject.file('src')] - task retroSourcesJar(type: Jar){ String str = sourceSets.main.output.classesDir; sourceSets.main.output.classesDir = "$buildDir/retrolambda/main" @@ -96,3 +93,4 @@ jar { } } + diff --git a/src/com/jfoenix/android/skins/JFXPasswordFieldSkinAndroid.java b/jfoenix/src/main/java/com/jfoenix/android/skins/JFXPasswordFieldSkinAndroid.java similarity index 97% rename from src/com/jfoenix/android/skins/JFXPasswordFieldSkinAndroid.java rename to jfoenix/src/main/java/com/jfoenix/android/skins/JFXPasswordFieldSkinAndroid.java index e57a7561..05033dae 100644 --- a/src/com/jfoenix/android/skins/JFXPasswordFieldSkinAndroid.java +++ b/jfoenix/src/main/java/com/jfoenix/android/skins/JFXPasswordFieldSkinAndroid.java @@ -1,488 +1,488 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package com.jfoenix.android.skins; - -import com.jfoenix.concurrency.JFXUtilities; -import com.jfoenix.controls.JFXPasswordField; -import com.jfoenix.skins.JFXPasswordFieldSkin; -import com.jfoenix.transitions.CachedTransition; -import com.jfoenix.validation.base.ValidatorBase; -import com.sun.javafx.scene.control.skin.TextFieldSkin; -import com.sun.javafx.scene.control.skin.TextFieldSkinAndroid; -import javafx.animation.Animation.Status; -import javafx.animation.*; -import javafx.application.Platform; -import javafx.beans.binding.Bindings; -import javafx.beans.binding.BooleanBinding; -import javafx.geometry.Insets; -import javafx.geometry.Pos; -import javafx.scene.Node; -import javafx.scene.control.Label; -import javafx.scene.layout.*; -import javafx.scene.paint.Color; -import javafx.scene.paint.Paint; -import javafx.scene.text.Text; -import javafx.scene.transform.Scale; -import javafx.util.Duration; - -import java.lang.reflect.Field; - -/** - *
- * Note: the implementation is a copy of the original {@link JFXPasswordFieldSkin} - * however it extends the JavaFXPorts text field android skin. - * - * @author Shadi Shaheen - * @version 2.0 - * @since 2017-01-25 - */ -public class JFXPasswordFieldSkinAndroid extends TextFieldSkinAndroid { - - private boolean invalid = true; - - private StackPane line = new StackPane(); - private StackPane focusedLine = new StackPane(); - - private Label errorLabel = new Label(); - private StackPane errorIcon = new StackPane(); - private HBox errorContainer; - private Pane textPane; - - private double initScale = 0.05; - private double oldErrorLabelHeight = -1; - private double initYLayout = -1; - private double initHeight = -1; - private boolean errorShown = false; - private double currentFieldHeight = -1; - private double errorLabelInitHeight = 0; - - private boolean heightChanged = false; - private StackPane promptContainer; - private Text promptText; - - private ParallelTransition transition; - private Timeline hideErrorAnimation; - private CachedTransition promptTextUpTransition; - private CachedTransition promptTextDownTransition; - private CachedTransition promptTextColorTransition; - - private Scale promptTextScale = new Scale(1,1,0,0); - private Scale scale = new Scale(initScale,1); - private Timeline linesAnimation = new Timeline( - new KeyFrame(Duration.ZERO, - new KeyValue(scale.xProperty(), initScale, Interpolator.EASE_BOTH), - new KeyValue(focusedLine.opacityProperty(), 0, Interpolator.EASE_BOTH)), - new KeyFrame(Duration.millis(1), - new KeyValue(focusedLine.opacityProperty(), 1, Interpolator.EASE_BOTH)), - new KeyFrame(Duration.millis(160), - new KeyValue(scale.xProperty(), 1, Interpolator.EASE_BOTH)) - ); - - private Paint oldPromptTextFill; - private BooleanBinding usePromptText = Bindings.createBooleanBinding(()-> usePromptText(), getSkinnable().textProperty(), getSkinnable().promptTextProperty()); - - public JFXPasswordFieldSkinAndroid(JFXPasswordField field) { - super(field); - // initial styles - field.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, null, null))); - field.setPadding(new Insets(4,0,4,0)); - - errorLabel.getStyleClass().add("error-label"); - errorLabel.setPadding(new Insets(4,0,0,0)); - errorLabel.setWrapText(true); - errorIcon.setTranslateY(3); - - StackPane errorLabelContainer = new StackPane(); - errorLabelContainer.getChildren().add(errorLabel); - StackPane.setAlignment(errorLabel, Pos.CENTER_LEFT); - - line.getStyleClass().add("input-line"); - getChildren().add(line); - focusedLine.getStyleClass().add("input-focused-line"); - getChildren().add(focusedLine); - - // draw lines - line.setPrefHeight(1); - line.setTranslateY(1); // translate = prefHeight + init_translation - line.setBackground(new Background(new BackgroundFill(((JFXPasswordField)getSkinnable()).getUnFocusColor(), - CornerRadii.EMPTY, Insets.EMPTY))); - if(getSkinnable().isDisabled()) { - line.setBorder(new Border(new BorderStroke(((JFXPasswordField) getSkinnable()).getUnFocusColor(), - BorderStrokeStyle.DASHED, CornerRadii.EMPTY, new BorderWidths(1)))); - line.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, - CornerRadii.EMPTY, Insets.EMPTY))); - } - - // focused line - focusedLine.setPrefHeight(2); - focusedLine.setTranslateY(0); // translate = prefHeight + init_translation(-1) - focusedLine.setBackground(new Background(new BackgroundFill(((JFXPasswordField)getSkinnable()).getFocusColor(), - CornerRadii.EMPTY, Insets.EMPTY))); - focusedLine.setOpacity(0); - focusedLine.getTransforms().add(scale); - - promptContainer = new StackPane(); - getChildren().add(promptContainer); - - errorContainer = new HBox(); - errorContainer.getChildren().setAll(errorLabelContainer, errorIcon); - HBox.setHgrow(errorLabelContainer, Priority.ALWAYS); - errorContainer.setSpacing(10); - errorContainer.setVisible(false); - errorContainer.setOpacity(0); - getChildren().add(errorContainer); - - // add listeners to show error label - errorLabel.heightProperty().addListener((o,oldVal,newVal)->{ - if(errorShown){ - if(oldErrorLabelHeight == -1) - oldErrorLabelHeight = errorLabelInitHeight = oldVal.doubleValue(); - heightChanged = true; - double newHeight = this.getSkinnable().getHeight() - oldErrorLabelHeight + newVal.doubleValue(); - // show the error - Timeline errorAnimation = new Timeline( - new KeyFrame(Duration.ZERO, new KeyValue(getSkinnable().minHeightProperty(), currentFieldHeight, Interpolator.EASE_BOTH)), - new KeyFrame(Duration.millis(160), - // text pane animation - new KeyValue(textPane.translateYProperty(), (initYLayout + textPane.getMaxHeight()/2) - newHeight/2, Interpolator.EASE_BOTH), - // animate the height change effect - new KeyValue(getSkinnable().minHeightProperty(), newHeight, Interpolator.EASE_BOTH))); - errorAnimation.play(); - // show the error label when finished - errorAnimation.setOnFinished(finish->new Timeline(new KeyFrame(Duration.millis(160),new KeyValue(errorContainer.opacityProperty(), 1, Interpolator.EASE_BOTH))).play()); - currentFieldHeight = newHeight; - oldErrorLabelHeight = newVal.doubleValue(); - } - }); - errorContainer.visibleProperty().addListener((o,oldVal,newVal)->{ - // show the error label if it's not shown - if(newVal) new Timeline(new KeyFrame(Duration.millis(160),new KeyValue(errorContainer.opacityProperty(), 1, Interpolator.EASE_BOTH))).play(); - }); - - - field.labelFloatProperty().addListener((o,oldVal,newVal)->{ - if(newVal) JFXUtilities.runInFX(()->createFloatingLabel()); - else promptText.visibleProperty().bind(usePromptText); - createFocusTransition(); - }); - - field.activeValidatorProperty().addListener((o,oldVal,newVal)->{ - if(textPane != null){ - if(!((JFXPasswordField)getSkinnable()).isDisableAnimation()){ - if(hideErrorAnimation!=null && hideErrorAnimation.getStatus().equals(Status.RUNNING)) - hideErrorAnimation.stop(); - if(newVal!=null){ - hideErrorAnimation = new Timeline(new KeyFrame(Duration.millis(160),new KeyValue(errorContainer.opacityProperty(), 0, Interpolator.EASE_BOTH))); - hideErrorAnimation.setOnFinished(finish->{ - errorContainer.setVisible(false); - JFXUtilities.runInFX(()->showError(newVal)); - }); - hideErrorAnimation.play(); - }else{ - JFXUtilities.runInFX(()->hideError()); - } - }else{ - if(newVal!=null) JFXUtilities.runInFXAndWait(()->showError(newVal)); - else JFXUtilities.runInFXAndWait(()->hideError()); - } - } - }); - - field.focusColorProperty().addListener((o,oldVal,newVal)->{ - if(newVal!=null) { - focusedLine.setBackground(new Background(new BackgroundFill(newVal, CornerRadii.EMPTY, Insets.EMPTY))); - if(((JFXPasswordField)getSkinnable()).isLabelFloat()){ - promptTextColorTransition = new CachedTransition(textPane, new Timeline( - new KeyFrame(Duration.millis(1300), - new KeyValue(promptTextFill, newVal, Interpolator.EASE_BOTH)))) - { - {setDelay(Duration.millis(0)); setCycleDuration(Duration.millis(160));} - protected void starting() {super.starting(); oldPromptTextFill = promptTextFill.get();} - }; - // reset transition - transition = null; - } - } - }); - field.unFocusColorProperty().addListener((o,oldVal,newVal)->{ - if(newVal!=null) - line.setBackground(new Background(new BackgroundFill(newVal, CornerRadii.EMPTY, Insets.EMPTY))); - }); - - // handle animation on focus gained/lost event - field.focusedProperty().addListener((o,oldVal,newVal) -> { - if (newVal) focus(); - else unFocus(); - }); - - // handle text changing at runtime - field.textProperty().addListener((o,oldVal,newVal)->{ - if(!getSkinnable().isFocused() && ((JFXPasswordField)getSkinnable()).isLabelFloat()){ - if(newVal == null || newVal.isEmpty()) animateFloatingLabel(false); - else animateFloatingLabel(true); - } - }); - - field.disabledProperty().addListener((o,oldVal,newVal) -> { - line.setBorder(newVal ? new Border(new BorderStroke(((JFXPasswordField)getSkinnable()).getUnFocusColor(), - BorderStrokeStyle.DASHED, CornerRadii.EMPTY, new BorderWidths(line.getHeight()))) : Border.EMPTY); - line.setBackground(new Background(new BackgroundFill( newVal? Color.TRANSPARENT : ((JFXPasswordField)getSkinnable()).getUnFocusColor(), - CornerRadii.EMPTY, Insets.EMPTY))); - }); - - // prevent setting prompt text fill to transparent when text field is focused (override java transparent color if the control was focused) - promptTextFill.addListener((o,oldVal,newVal)->{ - if(Color.TRANSPARENT.equals(newVal) && ((JFXPasswordField)getSkinnable()).isLabelFloat()) - promptTextFill.set(oldVal); - }); - - } - - @Override - protected void layoutChildren(final double x, final double y, final double w, final double h) { - super.layoutChildren(x, y, w, h); - - // change control properties if and only if animations are stopped - if((transition == null || transition.getStatus().equals(Status.STOPPED))){ - if(getSkinnable().isFocused() && ((JFXPasswordField)getSkinnable()).isLabelFloat()){ - promptTextFill.set(((JFXPasswordField)getSkinnable()).getFocusColor()); - } - } - - if(invalid){ - invalid = false; - textPane = ((Pane)this.getChildren().get(0)); - // create floating label - createFloatingLabel(); - // to position the prompt node properly - super.layoutChildren(x, y, w, h); - // update validation container - if(((JFXPasswordField)getSkinnable()).getActiveValidator()!=null) updateValidationError(); - // focus - createFocusTransition(); - if(getSkinnable().isFocused()) focus(); - } - - focusedLine.resizeRelocate(x, getSkinnable().getHeight(), w, focusedLine.prefHeight(-1)); - line.resizeRelocate(x, getSkinnable().getHeight(), w, line.prefHeight(-1)); - errorContainer.relocate(x, getSkinnable().getHeight() + focusedLine.getHeight()); - scale.setPivotX(w/2); - } - - private void updateValidationError() { - if(hideErrorAnimation!=null && hideErrorAnimation.getStatus().equals(Status.RUNNING)) - hideErrorAnimation.stop(); - hideErrorAnimation = new Timeline( - new KeyFrame(Duration.millis(160), - new KeyValue(errorContainer.opacityProperty(), 0, Interpolator.EASE_BOTH))); - hideErrorAnimation.setOnFinished(finish->{ - errorContainer.setVisible(false); - showError(((JFXPasswordField)getSkinnable()).getActiveValidator()); - }); - hideErrorAnimation.play(); - } - - - private void createFloatingLabel() { - if(((JFXPasswordField)getSkinnable()).isLabelFloat()){ - if(promptText == null){ - // get the prompt text node or create it - boolean triggerFloatLabel = false; - if(textPane.getChildren().get(0) instanceof Text) promptText = (Text) textPane.getChildren().get(0); - else{ - Field field; - try { - field = TextFieldSkin.class.getDeclaredField("promptNode"); - field.setAccessible(true); - createPromptNode(); - field.set(this, promptText); - // position the prompt node in its position - triggerFloatLabel = true; - } catch (NoSuchFieldException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (SecurityException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IllegalArgumentException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IllegalAccessException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - promptText.getTransforms().add(promptTextScale); - promptContainer.getChildren().add(promptText); - - if(triggerFloatLabel){ - promptText.setTranslateY(-textPane.getHeight()); - promptTextScale.setX(0.85); - promptTextScale.setY(0.85); - } - } - - promptTextUpTransition = new CachedTransition(textPane, new Timeline( - new KeyFrame(Duration.millis(1300), - new KeyValue(promptText.translateYProperty(), -textPane.getHeight(), Interpolator.EASE_BOTH), - new KeyValue(promptTextScale.xProperty(), 0.85 , Interpolator.EASE_BOTH), - new KeyValue(promptTextScale.yProperty(), 0.85 , Interpolator.EASE_BOTH)))){{ setDelay(Duration.millis(0)); setCycleDuration(Duration.millis(240)); }}; - - promptTextColorTransition = new CachedTransition(textPane, new Timeline( - new KeyFrame(Duration.millis(1300), - new KeyValue(promptTextFill, ((JFXPasswordField)getSkinnable()).getFocusColor(), Interpolator.EASE_BOTH)))) - { - { setDelay(Duration.millis(0)); setCycleDuration(Duration.millis(160)); } - protected void starting() {super.starting(); oldPromptTextFill = promptTextFill.get();}; - }; - - promptTextDownTransition = new CachedTransition(textPane, new Timeline( - new KeyFrame(Duration.millis(1300), - new KeyValue(promptText.translateYProperty(), 0, Interpolator.EASE_BOTH), - new KeyValue(promptTextScale.xProperty(), 1 , Interpolator.EASE_BOTH), - new KeyValue(promptTextScale.yProperty(), 1 , Interpolator.EASE_BOTH)))) - {{ setDelay(Duration.millis(0)); setCycleDuration(Duration.millis(240));}}; - promptTextDownTransition.setOnFinished((finish)->{ - promptText.setTranslateY(0); - promptTextScale.setX(1); - promptTextScale.setY(1); - }); - promptText.visibleProperty().unbind(); - promptText.visibleProperty().set(true); - } - } - - private void createPromptNode(){ - promptText = new Text(); - promptText.setManaged(false); - promptText.getStyleClass().add("text"); - promptText.visibleProperty().bind(usePromptText); - promptText.fontProperty().bind(getSkinnable().fontProperty()); - promptText.textProperty().bind(getSkinnable().promptTextProperty()); - promptText.fillProperty().bind(promptTextFill); - promptText.setLayoutX(1); - } - - private void focus(){ - /* - * in case the method request layout is not called before focused - * this is bug is reported while editing TreeTableView cells - */ - if(textPane == null){ - Platform.runLater(()->focus()); - }else{ - // create the focus animations - if(transition == null) createFocusTransition(); - transition.play(); - } - } - - private void createFocusTransition() { - transition = new ParallelTransition(); - if(((JFXPasswordField)getSkinnable()).isLabelFloat()){ - transition.getChildren().add(promptTextUpTransition); - transition.getChildren().add(promptTextColorTransition); - } - transition.getChildren().add(linesAnimation); - } - - private void unFocus() { - if(transition!=null) transition.stop(); - scale.setX(initScale); - focusedLine.setOpacity(0); - if(((JFXPasswordField)getSkinnable()).isLabelFloat() && oldPromptTextFill != null){ - promptTextFill.set(oldPromptTextFill); - if(usePromptText()) promptTextDownTransition.play(); - } - } - - /** - * this method is called when the text property is changed when the - * field is not focused (changed in code) - * @param up - */ - private void animateFloatingLabel(boolean up){ - if(promptText == null){ - Platform.runLater(()-> animateFloatingLabel(up)); - }else{ - if(transition!=null){ - transition.stop(); - transition.getChildren().remove(promptTextUpTransition); - transition = null; - } - if(up && promptText.getTranslateY() == 0){ - promptTextDownTransition.stop(); - promptTextUpTransition.play(); - }else if(!up){ - promptTextUpTransition.stop(); - promptTextDownTransition.play(); - } - } - } - - private boolean usePromptText() { - String txt = getSkinnable().getText(); - String promptTxt = getSkinnable().getPromptText(); - boolean hasPromptText = (txt == null || txt.isEmpty()) && promptTxt != null && !promptTxt.isEmpty() && !promptTextFill.get().equals(Color.TRANSPARENT); - return hasPromptText; - } - - private void showError(ValidatorBase validator){ - // set text in error label - errorLabel.setText(validator.getMessage()); - // show error icon - Node awsomeIcon = validator.getIcon(); - errorIcon.getChildren().clear(); - if(awsomeIcon!=null){ - errorIcon.getChildren().add(awsomeIcon); - StackPane.setAlignment(awsomeIcon, Pos.TOP_RIGHT); - } - // init only once, to fix the text pane from resizing - if(initYLayout == -1){ - textPane.setMaxHeight(textPane.getHeight()); - initYLayout = textPane.getBoundsInParent().getMinY(); - initHeight = getSkinnable().getHeight(); - currentFieldHeight = initHeight; - } - errorContainer.setVisible(true); - errorShown = true; - } - - private void hideError(){ - if(heightChanged){ - new Timeline(new KeyFrame(Duration.millis(160), new KeyValue(textPane.translateYProperty(), 0, Interpolator.EASE_BOTH))).play(); - // reset the height of text field - new Timeline(new KeyFrame(Duration.millis(160), new KeyValue(getSkinnable().minHeightProperty(), initHeight, Interpolator.EASE_BOTH))).play(); - heightChanged = false; - } - // clear error label text - errorLabel.setText(null); - oldErrorLabelHeight = errorLabelInitHeight; - // clear error icon - errorIcon.getChildren().clear(); - // reset the height of the text field - currentFieldHeight = initHeight; - // hide error container - errorContainer.setVisible(false); - errorShown = false; - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.jfoenix.android.skins; + +import com.jfoenix.concurrency.JFXUtilities; +import com.jfoenix.controls.JFXPasswordField; +import com.jfoenix.skins.JFXPasswordFieldSkin; +import com.jfoenix.transitions.CachedTransition; +import com.jfoenix.validation.base.ValidatorBase; +import com.sun.javafx.scene.control.skin.TextFieldSkin; +import com.sun.javafx.scene.control.skin.TextFieldSkinAndroid; +import javafx.animation.Animation.Status; +import javafx.animation.*; +import javafx.application.Platform; +import javafx.beans.binding.Bindings; +import javafx.beans.binding.BooleanBinding; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.layout.*; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; +import javafx.scene.text.Text; +import javafx.scene.transform.Scale; +import javafx.util.Duration; + +import java.lang.reflect.Field; + +/** + *
+ * Note: the implementation is a copy of the original {@link JFXPasswordFieldSkin} + * however it extends the JavaFXPorts text field android skin. + * + * @author Shadi Shaheen + * @version 2.0 + * @since 2017-01-25 + */ +public class JFXPasswordFieldSkinAndroid extends TextFieldSkinAndroid { + + private boolean invalid = true; + + private StackPane line = new StackPane(); + private StackPane focusedLine = new StackPane(); + + private Label errorLabel = new Label(); + private StackPane errorIcon = new StackPane(); + private HBox errorContainer; + private Pane textPane; + + private double initScale = 0.05; + private double oldErrorLabelHeight = -1; + private double initYLayout = -1; + private double initHeight = -1; + private boolean errorShown = false; + private double currentFieldHeight = -1; + private double errorLabelInitHeight = 0; + + private boolean heightChanged = false; + private StackPane promptContainer; + private Text promptText; + + private ParallelTransition transition; + private Timeline hideErrorAnimation; + private CachedTransition promptTextUpTransition; + private CachedTransition promptTextDownTransition; + private CachedTransition promptTextColorTransition; + + private Scale promptTextScale = new Scale(1,1,0,0); + private Scale scale = new Scale(initScale,1); + private Timeline linesAnimation = new Timeline( + new KeyFrame(Duration.ZERO, + new KeyValue(scale.xProperty(), initScale, Interpolator.EASE_BOTH), + new KeyValue(focusedLine.opacityProperty(), 0, Interpolator.EASE_BOTH)), + new KeyFrame(Duration.millis(1), + new KeyValue(focusedLine.opacityProperty(), 1, Interpolator.EASE_BOTH)), + new KeyFrame(Duration.millis(160), + new KeyValue(scale.xProperty(), 1, Interpolator.EASE_BOTH)) + ); + + private Paint oldPromptTextFill; + private BooleanBinding usePromptText = Bindings.createBooleanBinding(()-> usePromptText(), getSkinnable().textProperty(), getSkinnable().promptTextProperty()); + + public JFXPasswordFieldSkinAndroid(JFXPasswordField field) { + super(field); + // initial styles + field.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, null, null))); + field.setPadding(new Insets(4,0,4,0)); + + errorLabel.getStyleClass().add("error-label"); + errorLabel.setPadding(new Insets(4,0,0,0)); + errorLabel.setWrapText(true); + errorIcon.setTranslateY(3); + + StackPane errorLabelContainer = new StackPane(); + errorLabelContainer.getChildren().add(errorLabel); + StackPane.setAlignment(errorLabel, Pos.CENTER_LEFT); + + line.getStyleClass().add("input-line"); + getChildren().add(line); + focusedLine.getStyleClass().add("input-focused-line"); + getChildren().add(focusedLine); + + // draw lines + line.setPrefHeight(1); + line.setTranslateY(1); // translate = prefHeight + init_translation + line.setBackground(new Background(new BackgroundFill(((JFXPasswordField)getSkinnable()).getUnFocusColor(), + CornerRadii.EMPTY, Insets.EMPTY))); + if(getSkinnable().isDisabled()) { + line.setBorder(new Border(new BorderStroke(((JFXPasswordField) getSkinnable()).getUnFocusColor(), + BorderStrokeStyle.DASHED, CornerRadii.EMPTY, new BorderWidths(1)))); + line.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, + CornerRadii.EMPTY, Insets.EMPTY))); + } + + // focused line + focusedLine.setPrefHeight(2); + focusedLine.setTranslateY(0); // translate = prefHeight + init_translation(-1) + focusedLine.setBackground(new Background(new BackgroundFill(((JFXPasswordField)getSkinnable()).getFocusColor(), + CornerRadii.EMPTY, Insets.EMPTY))); + focusedLine.setOpacity(0); + focusedLine.getTransforms().add(scale); + + promptContainer = new StackPane(); + getChildren().add(promptContainer); + + errorContainer = new HBox(); + errorContainer.getChildren().setAll(errorLabelContainer, errorIcon); + HBox.setHgrow(errorLabelContainer, Priority.ALWAYS); + errorContainer.setSpacing(10); + errorContainer.setVisible(false); + errorContainer.setOpacity(0); + getChildren().add(errorContainer); + + // add listeners to show error label + errorLabel.heightProperty().addListener((o,oldVal,newVal)->{ + if(errorShown){ + if(oldErrorLabelHeight == -1) + oldErrorLabelHeight = errorLabelInitHeight = oldVal.doubleValue(); + heightChanged = true; + double newHeight = this.getSkinnable().getHeight() - oldErrorLabelHeight + newVal.doubleValue(); + // show the error + Timeline errorAnimation = new Timeline( + new KeyFrame(Duration.ZERO, new KeyValue(getSkinnable().minHeightProperty(), currentFieldHeight, Interpolator.EASE_BOTH)), + new KeyFrame(Duration.millis(160), + // text pane animation + new KeyValue(textPane.translateYProperty(), (initYLayout + textPane.getMaxHeight()/2) - newHeight/2, Interpolator.EASE_BOTH), + // animate the height change effect + new KeyValue(getSkinnable().minHeightProperty(), newHeight, Interpolator.EASE_BOTH))); + errorAnimation.play(); + // show the error label when finished + errorAnimation.setOnFinished(finish->new Timeline(new KeyFrame(Duration.millis(160),new KeyValue(errorContainer.opacityProperty(), 1, Interpolator.EASE_BOTH))).play()); + currentFieldHeight = newHeight; + oldErrorLabelHeight = newVal.doubleValue(); + } + }); + errorContainer.visibleProperty().addListener((o,oldVal,newVal)->{ + // show the error label if it's not shown + if(newVal) new Timeline(new KeyFrame(Duration.millis(160),new KeyValue(errorContainer.opacityProperty(), 1, Interpolator.EASE_BOTH))).play(); + }); + + + field.labelFloatProperty().addListener((o,oldVal,newVal)->{ + if(newVal) JFXUtilities.runInFX(()->createFloatingLabel()); + else promptText.visibleProperty().bind(usePromptText); + createFocusTransition(); + }); + + field.activeValidatorProperty().addListener((o,oldVal,newVal)->{ + if(textPane != null){ + if(!((JFXPasswordField)getSkinnable()).isDisableAnimation()){ + if(hideErrorAnimation!=null && hideErrorAnimation.getStatus().equals(Status.RUNNING)) + hideErrorAnimation.stop(); + if(newVal!=null){ + hideErrorAnimation = new Timeline(new KeyFrame(Duration.millis(160),new KeyValue(errorContainer.opacityProperty(), 0, Interpolator.EASE_BOTH))); + hideErrorAnimation.setOnFinished(finish->{ + errorContainer.setVisible(false); + JFXUtilities.runInFX(()->showError(newVal)); + }); + hideErrorAnimation.play(); + }else{ + JFXUtilities.runInFX(()->hideError()); + } + }else{ + if(newVal!=null) JFXUtilities.runInFXAndWait(()->showError(newVal)); + else JFXUtilities.runInFXAndWait(()->hideError()); + } + } + }); + + field.focusColorProperty().addListener((o,oldVal,newVal)->{ + if(newVal!=null) { + focusedLine.setBackground(new Background(new BackgroundFill(newVal, CornerRadii.EMPTY, Insets.EMPTY))); + if(((JFXPasswordField)getSkinnable()).isLabelFloat()){ + promptTextColorTransition = new CachedTransition(textPane, new Timeline( + new KeyFrame(Duration.millis(1300), + new KeyValue(promptTextFill, newVal, Interpolator.EASE_BOTH)))) + { + {setDelay(Duration.millis(0)); setCycleDuration(Duration.millis(160));} + protected void starting() {super.starting(); oldPromptTextFill = promptTextFill.get();} + }; + // reset transition + transition = null; + } + } + }); + field.unFocusColorProperty().addListener((o,oldVal,newVal)->{ + if(newVal!=null) + line.setBackground(new Background(new BackgroundFill(newVal, CornerRadii.EMPTY, Insets.EMPTY))); + }); + + // handle animation on focus gained/lost event + field.focusedProperty().addListener((o,oldVal,newVal) -> { + if (newVal) focus(); + else unFocus(); + }); + + // handle text changing at runtime + field.textProperty().addListener((o,oldVal,newVal)->{ + if(!getSkinnable().isFocused() && ((JFXPasswordField)getSkinnable()).isLabelFloat()){ + if(newVal == null || newVal.isEmpty()) animateFloatingLabel(false); + else animateFloatingLabel(true); + } + }); + + field.disabledProperty().addListener((o,oldVal,newVal) -> { + line.setBorder(newVal ? new Border(new BorderStroke(((JFXPasswordField)getSkinnable()).getUnFocusColor(), + BorderStrokeStyle.DASHED, CornerRadii.EMPTY, new BorderWidths(line.getHeight()))) : Border.EMPTY); + line.setBackground(new Background(new BackgroundFill( newVal? Color.TRANSPARENT : ((JFXPasswordField)getSkinnable()).getUnFocusColor(), + CornerRadii.EMPTY, Insets.EMPTY))); + }); + + // prevent setting prompt text fill to transparent when text field is focused (override java transparent color if the control was focused) + promptTextFill.addListener((o,oldVal,newVal)->{ + if(Color.TRANSPARENT.equals(newVal) && ((JFXPasswordField)getSkinnable()).isLabelFloat()) + promptTextFill.set(oldVal); + }); + + } + + @Override + protected void layoutChildren(final double x, final double y, final double w, final double h) { + super.layoutChildren(x, y, w, h); + + // change control properties if and only if animations are stopped + if((transition == null || transition.getStatus().equals(Status.STOPPED))){ + if(getSkinnable().isFocused() && ((JFXPasswordField)getSkinnable()).isLabelFloat()){ + promptTextFill.set(((JFXPasswordField)getSkinnable()).getFocusColor()); + } + } + + if(invalid){ + invalid = false; + textPane = ((Pane)this.getChildren().get(0)); + // create floating label + createFloatingLabel(); + // to position the prompt node properly + super.layoutChildren(x, y, w, h); + // update validation container + if(((JFXPasswordField)getSkinnable()).getActiveValidator()!=null) updateValidationError(); + // focus + createFocusTransition(); + if(getSkinnable().isFocused()) focus(); + } + + focusedLine.resizeRelocate(x, getSkinnable().getHeight(), w, focusedLine.prefHeight(-1)); + line.resizeRelocate(x, getSkinnable().getHeight(), w, line.prefHeight(-1)); + errorContainer.relocate(x, getSkinnable().getHeight() + focusedLine.getHeight()); + scale.setPivotX(w/2); + } + + private void updateValidationError() { + if(hideErrorAnimation!=null && hideErrorAnimation.getStatus().equals(Status.RUNNING)) + hideErrorAnimation.stop(); + hideErrorAnimation = new Timeline( + new KeyFrame(Duration.millis(160), + new KeyValue(errorContainer.opacityProperty(), 0, Interpolator.EASE_BOTH))); + hideErrorAnimation.setOnFinished(finish->{ + errorContainer.setVisible(false); + showError(((JFXPasswordField)getSkinnable()).getActiveValidator()); + }); + hideErrorAnimation.play(); + } + + + private void createFloatingLabel() { + if(((JFXPasswordField)getSkinnable()).isLabelFloat()){ + if(promptText == null){ + // get the prompt text node or create it + boolean triggerFloatLabel = false; + if(textPane.getChildren().get(0) instanceof Text) promptText = (Text) textPane.getChildren().get(0); + else{ + Field field; + try { + field = TextFieldSkin.class.getDeclaredField("promptNode"); + field.setAccessible(true); + createPromptNode(); + field.set(this, promptText); + // position the prompt node in its position + triggerFloatLabel = true; + } catch (NoSuchFieldException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (SecurityException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalAccessException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + promptText.getTransforms().add(promptTextScale); + promptContainer.getChildren().add(promptText); + + if(triggerFloatLabel){ + promptText.setTranslateY(-textPane.getHeight()); + promptTextScale.setX(0.85); + promptTextScale.setY(0.85); + } + } + + promptTextUpTransition = new CachedTransition(textPane, new Timeline( + new KeyFrame(Duration.millis(1300), + new KeyValue(promptText.translateYProperty(), -textPane.getHeight(), Interpolator.EASE_BOTH), + new KeyValue(promptTextScale.xProperty(), 0.85 , Interpolator.EASE_BOTH), + new KeyValue(promptTextScale.yProperty(), 0.85 , Interpolator.EASE_BOTH)))){{ setDelay(Duration.millis(0)); setCycleDuration(Duration.millis(240)); }}; + + promptTextColorTransition = new CachedTransition(textPane, new Timeline( + new KeyFrame(Duration.millis(1300), + new KeyValue(promptTextFill, ((JFXPasswordField)getSkinnable()).getFocusColor(), Interpolator.EASE_BOTH)))) + { + { setDelay(Duration.millis(0)); setCycleDuration(Duration.millis(160)); } + protected void starting() {super.starting(); oldPromptTextFill = promptTextFill.get();}; + }; + + promptTextDownTransition = new CachedTransition(textPane, new Timeline( + new KeyFrame(Duration.millis(1300), + new KeyValue(promptText.translateYProperty(), 0, Interpolator.EASE_BOTH), + new KeyValue(promptTextScale.xProperty(), 1 , Interpolator.EASE_BOTH), + new KeyValue(promptTextScale.yProperty(), 1 , Interpolator.EASE_BOTH)))) + {{ setDelay(Duration.millis(0)); setCycleDuration(Duration.millis(240));}}; + promptTextDownTransition.setOnFinished((finish)->{ + promptText.setTranslateY(0); + promptTextScale.setX(1); + promptTextScale.setY(1); + }); + promptText.visibleProperty().unbind(); + promptText.visibleProperty().set(true); + } + } + + private void createPromptNode(){ + promptText = new Text(); + promptText.setManaged(false); + promptText.getStyleClass().add("text"); + promptText.visibleProperty().bind(usePromptText); + promptText.fontProperty().bind(getSkinnable().fontProperty()); + promptText.textProperty().bind(getSkinnable().promptTextProperty()); + promptText.fillProperty().bind(promptTextFill); + promptText.setLayoutX(1); + } + + private void focus(){ + /* + * in case the method request layout is not called before focused + * this is bug is reported while editing TreeTableView cells + */ + if(textPane == null){ + Platform.runLater(()->focus()); + }else{ + // create the focus animations + if(transition == null) createFocusTransition(); + transition.play(); + } + } + + private void createFocusTransition() { + transition = new ParallelTransition(); + if(((JFXPasswordField)getSkinnable()).isLabelFloat()){ + transition.getChildren().add(promptTextUpTransition); + transition.getChildren().add(promptTextColorTransition); + } + transition.getChildren().add(linesAnimation); + } + + private void unFocus() { + if(transition!=null) transition.stop(); + scale.setX(initScale); + focusedLine.setOpacity(0); + if(((JFXPasswordField)getSkinnable()).isLabelFloat() && oldPromptTextFill != null){ + promptTextFill.set(oldPromptTextFill); + if(usePromptText()) promptTextDownTransition.play(); + } + } + + /** + * this method is called when the text property is changed when the + * field is not focused (changed in code) + * @param up + */ + private void animateFloatingLabel(boolean up){ + if(promptText == null){ + Platform.runLater(()-> animateFloatingLabel(up)); + }else{ + if(transition!=null){ + transition.stop(); + transition.getChildren().remove(promptTextUpTransition); + transition = null; + } + if(up && promptText.getTranslateY() == 0){ + promptTextDownTransition.stop(); + promptTextUpTransition.play(); + }else if(!up){ + promptTextUpTransition.stop(); + promptTextDownTransition.play(); + } + } + } + + private boolean usePromptText() { + String txt = getSkinnable().getText(); + String promptTxt = getSkinnable().getPromptText(); + boolean hasPromptText = (txt == null || txt.isEmpty()) && promptTxt != null && !promptTxt.isEmpty() && !promptTextFill.get().equals(Color.TRANSPARENT); + return hasPromptText; + } + + private void showError(ValidatorBase validator){ + // set text in error label + errorLabel.setText(validator.getMessage()); + // show error icon + Node awsomeIcon = validator.getIcon(); + errorIcon.getChildren().clear(); + if(awsomeIcon!=null){ + errorIcon.getChildren().add(awsomeIcon); + StackPane.setAlignment(awsomeIcon, Pos.TOP_RIGHT); + } + // init only once, to fix the text pane from resizing + if(initYLayout == -1){ + textPane.setMaxHeight(textPane.getHeight()); + initYLayout = textPane.getBoundsInParent().getMinY(); + initHeight = getSkinnable().getHeight(); + currentFieldHeight = initHeight; + } + errorContainer.setVisible(true); + errorShown = true; + } + + private void hideError(){ + if(heightChanged){ + new Timeline(new KeyFrame(Duration.millis(160), new KeyValue(textPane.translateYProperty(), 0, Interpolator.EASE_BOTH))).play(); + // reset the height of text field + new Timeline(new KeyFrame(Duration.millis(160), new KeyValue(getSkinnable().minHeightProperty(), initHeight, Interpolator.EASE_BOTH))).play(); + heightChanged = false; + } + // clear error label text + errorLabel.setText(null); + oldErrorLabelHeight = errorLabelInitHeight; + // clear error icon + errorIcon.getChildren().clear(); + // reset the height of the text field + currentFieldHeight = initHeight; + // hide error container + errorContainer.setVisible(false); + errorShown = false; + } +} diff --git a/src/com/jfoenix/android/skins/JFXTextAreaSkinAndroid.java b/jfoenix/src/main/java/com/jfoenix/android/skins/JFXTextAreaSkinAndroid.java similarity index 97% rename from src/com/jfoenix/android/skins/JFXTextAreaSkinAndroid.java rename to jfoenix/src/main/java/com/jfoenix/android/skins/JFXTextAreaSkinAndroid.java index 1b145ac8..def00dde 100644 --- a/src/com/jfoenix/android/skins/JFXTextAreaSkinAndroid.java +++ b/jfoenix/src/main/java/com/jfoenix/android/skins/JFXTextAreaSkinAndroid.java @@ -1,524 +1,524 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package com.jfoenix.android.skins; - -import com.jfoenix.concurrency.JFXUtilities; -import com.jfoenix.controls.JFXTextArea; -import com.jfoenix.transitions.CachedTransition; -import com.jfoenix.validation.base.ValidatorBase; -import com.sun.javafx.scene.control.skin.TextAreaSkin; -import com.sun.javafx.scene.control.skin.TextAreaSkinAndroid; -import javafx.animation.Animation.Status; -import javafx.animation.*; -import javafx.application.Platform; -import javafx.beans.binding.Bindings; -import javafx.beans.binding.BooleanBinding; -import javafx.geometry.Insets; -import javafx.geometry.Pos; -import javafx.scene.Node; -import javafx.scene.control.Label; -import javafx.scene.control.ScrollPane; -import javafx.scene.layout.*; -import javafx.scene.paint.Color; -import javafx.scene.paint.Paint; -import javafx.scene.text.Text; -import javafx.scene.transform.Scale; -import javafx.util.Duration; - -import java.lang.reflect.Field; - -/** - *
- * Note: the implementation is a copy of the original {@link com.jfoenix.skins.JFXTextAreaSkin JFXTextAreaSkin} - * however it extends the JavaFXPorts text area android skin. - * - * @author Shadi Shaheen - * @version 2.0 - * @since 2017-01-25 - */ -public class JFXTextAreaSkinAndroid extends TextAreaSkinAndroid { - - private static Background transparentBackground = new Background( - new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY), - new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY), - new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY), - new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY)); - - private boolean invalid = true; - - private StackPane line = new StackPane(); - private StackPane focusedLine = new StackPane(); - - private Label errorLabel = new Label(); - private StackPane errorIcon = new StackPane(); - private HBox errorContainer; - private ScrollPane scrollPane; - - private double initScale = 0.05; - private double oldErrorLabelHeight = -1; - // private Region textPane; - private double initYLayout = -1; - private double initHeight = -1; - private boolean errorShown = false; - private double currentFieldHeight = -1; - private double errorLabelInitHeight = 0; - private boolean heightChanged = false; - - private Pane promptContainer; - private Text promptText; - - private CachedTransition promptTextUpTransition; - private CachedTransition promptTextDownTransition; - private CachedTransition promptTextColorTransition; - - private Timeline hideErrorAnimation; - private ParallelTransition transition; - - private Scale promptTextScale = new Scale(1,1,0,0); - private Scale scale = new Scale(initScale,1); - private Timeline linesAnimation = new Timeline( - new KeyFrame(Duration.ZERO, - new KeyValue(scale.xProperty(), initScale, Interpolator.EASE_BOTH), - new KeyValue(focusedLine.opacityProperty(), 0, Interpolator.EASE_BOTH)), - new KeyFrame(Duration.millis(1), - new KeyValue(focusedLine.opacityProperty(), 1, Interpolator.EASE_BOTH)), - new KeyFrame(Duration.millis(160), - new KeyValue(scale.xProperty(), 1, Interpolator.EASE_BOTH)) - ); - - private Paint oldPromptTextFill; - private BooleanBinding usePromptText = Bindings.createBooleanBinding(()-> usePromptText(), getSkinnable().textProperty(), getSkinnable().promptTextProperty()); - - public JFXTextAreaSkinAndroid(JFXTextArea textArea) { - super(textArea); - // init text area properties - scrollPane = (ScrollPane) getChildren().get(0); - ((Region)scrollPane.getContent()).setPadding(new Insets(0)); - // hide text area borders - scrollPane.setBackground(transparentBackground); - ((Region)scrollPane.getContent()).setBackground(transparentBackground); - getSkinnable().setBackground(transparentBackground); - textArea.setWrapText(true); - - errorLabel.getStyleClass().add("error-label"); - errorLabel.setPadding(new Insets(4,0,0,0)); - errorLabel.setWrapText(true); - errorIcon.setTranslateY(3); - StackPane errorLabelContainer = new StackPane(); - errorLabelContainer.getChildren().add(errorLabel); - StackPane.setAlignment(errorLabel, Pos.CENTER_LEFT); - - promptContainer = new StackPane(); - - line.getStyleClass().add("input-line"); - focusedLine.getStyleClass().add("input-focused-line"); - // draw lines - line.setPrefHeight(1); - line.setTranslateY(1 + 4 + 2); // translate = prefHeight + init_translation - line.setBackground(new Background(new BackgroundFill(((JFXTextArea)getSkinnable()).getUnFocusColor(), - CornerRadii.EMPTY, Insets.EMPTY))); - if(getSkinnable().isDisabled()) { - line.setBorder(new Border(new BorderStroke(((JFXTextArea) getSkinnable()).getUnFocusColor(), - BorderStrokeStyle.DASHED, CornerRadii.EMPTY, new BorderWidths(1)))); - line.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, - CornerRadii.EMPTY, Insets.EMPTY))); - } - - // focused line - focusedLine.setPrefHeight(2); - focusedLine.setTranslateY(0 + 4 + 2); // translate = prefHeight + init_translation(-1) - focusedLine.setBackground(new Background(new BackgroundFill(((JFXTextArea)getSkinnable()).getFocusColor(), - CornerRadii.EMPTY, Insets.EMPTY))); - focusedLine.setOpacity(0); - focusedLine.getTransforms().add(scale); - - errorContainer = new HBox(); - errorContainer.getChildren().setAll(errorLabelContainer, errorIcon); - HBox.setHgrow(errorLabelContainer, Priority.ALWAYS); - - errorContainer.setSpacing(10); - errorContainer.setVisible(false); - errorContainer.setOpacity(0); - - getChildren().addAll(line, focusedLine, promptContainer, errorContainer); - - getSkinnable().setBackground(transparentBackground); - - // errorContainer.layoutXProperty().bind(scrollPane.layoutXProperty()); - // errorContainer.layoutYProperty().bind(scrollPane.layoutYProperty()); - - - // add listeners to show error label - errorLabel.heightProperty().addListener((o,oldVal,newVal)->{ - if(errorShown){ - if(oldErrorLabelHeight == -1) - oldErrorLabelHeight = errorLabelInitHeight = oldVal.doubleValue(); - - heightChanged = true; - double newHeight = this.getSkinnable().getHeight() - oldErrorLabelHeight + newVal.doubleValue(); - // // show the error - // Timeline errorAnimation = new Timeline( - // new KeyFrame(Duration.ZERO, new KeyValue(getSkinnable().minHeightProperty(), currentFieldHeight, Interpolator.EASE_BOTH)), - // new KeyFrame(Duration.millis(160), - // // text pane animation - // new KeyValue(mainPane.translateYProperty(), (initYlayout + mainPane.getMaxHeight()/2) - newHeight/2, Interpolator.EASE_BOTH), - // // animate the height change effect - // new KeyValue(getSkinnable().minHeightProperty(), newHeight, Interpolator.EASE_BOTH))); - // errorAnimation.play(); - // // show the error label when finished - // errorAnimation.setOnFinished(finish->new Timeline(new KeyFrame(Duration.millis(160),new KeyValue(errorContainer.opacityProperty(), 1, Interpolator.EASE_BOTH))).play()); - currentFieldHeight = newHeight; - oldErrorLabelHeight = newVal.doubleValue(); - } - }); - errorContainer.visibleProperty().addListener((o,oldVal,newVal)->{ - // show the error label if it's not shown - if(newVal) new Timeline(new KeyFrame(Duration.millis(160),new KeyValue(errorContainer.opacityProperty(), 1, Interpolator.EASE_BOTH))).play(); - }); - - - textArea.labelFloatProperty().addListener((o,oldVal,newVal)->{ - if(newVal) JFXUtilities.runInFX(()->createFloatingLabel()); - else promptText.visibleProperty().bind(usePromptText); - createFocusTransition(); - }); - - textArea.activeValidatorProperty().addListener((o,oldVal,newVal)->{ - if(scrollPane != null){ - if(!((JFXTextArea)getSkinnable()).isDisableAnimation()){ - if(hideErrorAnimation!=null && hideErrorAnimation.getStatus().equals(Status.RUNNING)) - hideErrorAnimation.stop(); - if(newVal!=null){ - hideErrorAnimation = new Timeline(new KeyFrame(Duration.millis(160),new KeyValue(errorContainer.opacityProperty(), 0, Interpolator.EASE_BOTH))); - hideErrorAnimation.setOnFinished(finish->{ - errorContainer.setVisible(false); - JFXUtilities.runInFX(()->showError(newVal)); - }); - hideErrorAnimation.play(); - }else{ - JFXUtilities.runInFX(()->hideError()); - } - }else{ - if(newVal!=null) JFXUtilities.runInFXAndWait(()->showError(newVal)); - else JFXUtilities.runInFXAndWait(()->hideError()); - } - } - }); - - textArea.focusColorProperty().addListener((o,oldVal,newVal)->{ - if(newVal!=null) { - focusedLine.setBackground(new Background(new BackgroundFill(newVal, CornerRadii.EMPTY, Insets.EMPTY))); - if(((JFXTextArea)getSkinnable()).isLabelFloat()){ - promptTextColorTransition = new CachedTransition(promptContainer, new Timeline( - new KeyFrame(Duration.millis(1300),new KeyValue(promptTextFill, newVal, Interpolator.EASE_BOTH)))) - { - {setDelay(Duration.millis(0)); setCycleDuration(Duration.millis(160));} - protected void starting() {super.starting(); oldPromptTextFill = promptTextFill.get();} - }; - // reset transition - transition = null; - } - } - }); - textArea.unFocusColorProperty().addListener((o,oldVal,newVal)->{ - if(newVal!=null) - line.setBackground(new Background(new BackgroundFill(newVal, CornerRadii.EMPTY, Insets.EMPTY))); - }); - - // handle animation on focus gained/lost event - textArea.focusedProperty().addListener((o,oldVal,newVal) -> { - if (newVal) focus(); - else unFocus(); - }); - - // handle text changing at runtime - textArea.textProperty().addListener((o,oldVal,newVal)->{ - if(!getSkinnable().isFocused() && ((JFXTextArea)getSkinnable()).isLabelFloat()){ - if(newVal == null || newVal.isEmpty()) animateFLoatingLabel(false); - else animateFLoatingLabel(true); - } - }); - - textArea.backgroundProperty().addListener((o,oldVal,newVal)->{ - // Force transparent background - if(oldVal == transparentBackground && newVal != transparentBackground){ - textArea.setBackground(transparentBackground); - } - }); - - textArea.disabledProperty().addListener((o,oldVal,newVal) -> { - line.setBorder(newVal ? new Border(new BorderStroke(((JFXTextArea)getSkinnable()).getUnFocusColor(), - BorderStrokeStyle.DASHED, CornerRadii.EMPTY, new BorderWidths(line.getHeight()))) : Border.EMPTY); - line.setBackground(new Background(new BackgroundFill( newVal? Color.TRANSPARENT : ((JFXTextArea)getSkinnable()).getUnFocusColor(), - CornerRadii.EMPTY, Insets.EMPTY))); - }); - - // prevent setting prompt text fill to transparent when text field is focused (override java transparent color if the control was focused) - promptTextFill.addListener((o,oldVal,newVal)->{ - if(Color.TRANSPARENT.equals(newVal) && ((JFXTextArea)getSkinnable()).isLabelFloat()){ - promptTextFill.set(oldVal); - } - }); - } - - - @Override - protected void layoutChildren(final double x, final double y, final double w, final double h) { - super.layoutChildren(x, y, w, h); - - // change control properties if and only if animations are stopped - if((transition == null || transition.getStatus().equals(Status.STOPPED))){ - if(getSkinnable().isFocused() && ((JFXTextArea)getSkinnable()).isLabelFloat()){ - promptTextFill.set(((JFXTextArea)getSkinnable()).getFocusColor()); - } - } - - if(invalid){ - invalid = false; -// // set the default background of text area viewport to white - Region viewPort = ((Region)scrollPane.getChildrenUnmodifiable().get(0)); - viewPort.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY))); - // reapply css of scroll pane in case set by the user - viewPort.applyCss(); - -// errorLabel.maxWidthProperty().bind(Bindings.createDoubleBinding(()->getSkinnable().getWidth()/1.14, getSkinnable().widthProperty())); - - // create floating label - createFloatingLabel(); - // to position the prompt node properly - super.layoutChildren(x, y, w, h); - // update validation container - if(((JFXTextArea)getSkinnable()).getActiveValidator()!=null) updateValidationError(); - // focus - createFocusTransition(); - if(getSkinnable().isFocused()) focus(); - } - - focusedLine.resizeRelocate(x, h-focusedLine.prefHeight(-1), w, focusedLine.prefHeight(-1)); - line.resizeRelocate(x, h-focusedLine.prefHeight(-1), w, line.prefHeight(-1)); - errorContainer.resizeRelocate(x, y, w, -1); - errorContainer.setTranslateY(h + focusedLine.getHeight() + 4); - scale.setPivotX(w/2); - } - - private void updateValidationError() { - if(hideErrorAnimation!=null && hideErrorAnimation.getStatus().equals(Status.RUNNING)) - hideErrorAnimation.stop(); - hideErrorAnimation = new Timeline( - new KeyFrame(Duration.millis(160), - new KeyValue(errorContainer.opacityProperty(), 0, Interpolator.EASE_BOTH))); - hideErrorAnimation.setOnFinished(finish->{ - errorContainer.setVisible(false); - showError(((JFXTextArea)getSkinnable()).getActiveValidator()); - }); - hideErrorAnimation.play(); - } - - private void createFloatingLabel() { - if(((JFXTextArea)getSkinnable()).isLabelFloat()){ - if(promptText == null){ - // get the prompt text node or create it - boolean triggerFloatLabel = false; - if(((Region)scrollPane.getContent()).getChildrenUnmodifiable().get(0) instanceof Text) promptText = (Text) ((Region)scrollPane.getContent()).getChildrenUnmodifiable().get(0); - else{ - Field field; - try { - field = TextAreaSkin.class.getDeclaredField("promptNode"); - field.setAccessible(true); - createPromptNode(); - field.set(this, promptText); - // position the prompt node in its position - triggerFloatLabel = true; - oldPromptTextFill = promptTextFill.get(); - } catch (NoSuchFieldException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (SecurityException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IllegalArgumentException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IllegalAccessException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - // fixed issue text area is being resized when the content is excedeing its width - promptText.wrappingWidthProperty().addListener((o,oldval,newVal)->{ - if(newVal.doubleValue() > getSkinnable().getWidth()) - promptText.setWrappingWidth(getSkinnable().getWidth()); - }); - - promptText.getTransforms().add(promptTextScale); - promptContainer.getChildren().add(promptText); - if(triggerFloatLabel){ - promptText.setTranslateY(-promptText.getBoundsInLocal().getHeight()-2); - promptTextScale.setX(0.85); - promptTextScale.setY(0.85); - } - } - - // create prompt animations - promptTextUpTransition = new CachedTransition(promptContainer, new Timeline( - new KeyFrame(Duration.millis(1300), - new KeyValue(promptText.translateYProperty(), -promptText.getLayoutBounds().getHeight()-2, Interpolator.EASE_BOTH), - new KeyValue(promptTextScale.xProperty(), 0.85 , Interpolator.EASE_BOTH), - new KeyValue(promptTextScale.yProperty(), 0.85 , Interpolator.EASE_BOTH)))){{ setDelay(Duration.millis(0)); setCycleDuration(Duration.millis(240)); }}; - - promptTextColorTransition = new CachedTransition(promptContainer, new Timeline( - new KeyFrame(Duration.millis(1300),new KeyValue(promptTextFill, ((JFXTextArea)getSkinnable()).getFocusColor(), Interpolator.EASE_BOTH)))) - {{ setDelay(Duration.millis(0)); setCycleDuration(Duration.millis(160)); } - protected void starting() {super.starting(); oldPromptTextFill = promptTextFill.get();};}; - - promptTextDownTransition = new CachedTransition(promptContainer, new Timeline( - new KeyFrame(Duration.millis(1300), - new KeyValue(promptText.translateYProperty(), 0, Interpolator.EASE_BOTH), - new KeyValue(promptTextScale.xProperty(),1 , Interpolator.EASE_BOTH), - new KeyValue(promptTextScale.yProperty(),1 , Interpolator.EASE_BOTH)) - )){{ setDelay(Duration.millis(0)); setCycleDuration(Duration.millis(240)); }}; - promptTextDownTransition.setOnFinished((finish)->{ - promptText.setTranslateY(0); - promptTextScale.setX(1); - promptTextScale.setY(1); - }); - - promptText.visibleProperty().unbind(); - promptText.visibleProperty().set(true); - } - } - - private void createPromptNode(){ - promptText = new Text(); - promptText.setManaged(false); - promptText.getStyleClass().add("text"); - promptText.visibleProperty().bind(usePromptText); - promptText.fontProperty().bind(getSkinnable().fontProperty()); - promptText.textProperty().bind(getSkinnable().promptTextProperty()); - promptText.fillProperty().bind(promptTextFill); - promptText.setLayoutX(1); - } - - private void focus(){ - /* - * in case the method request layout is not called before focused - * this is bug is reported while editing treetableview cells - */ - if(scrollPane == null){ - Platform.runLater(()->focus()); - }else{ - // create the focus animations - if(transition == null) createFocusTransition(); - transition.play(); - } - } - - private void createFocusTransition() { - transition = new ParallelTransition(); - if(((JFXTextArea)getSkinnable()).isLabelFloat()){ - transition.getChildren().add(promptTextUpTransition); - transition.getChildren().add(promptTextColorTransition); - } - transition.getChildren().add(linesAnimation); - } - - private void unFocus() { - if(transition!=null) transition.stop(); - scale.setX(initScale); - focusedLine.setOpacity(0); - if(((JFXTextArea)getSkinnable()).isLabelFloat() && oldPromptTextFill != null){ - promptTextFill.set(oldPromptTextFill); - if(usePromptText()) promptTextDownTransition.play(); - } - } - - /** - * this method is called when the text property is changed when the - * field is not focused (changed in code) - * @param up - */ - private void animateFLoatingLabel(boolean up){ - if(promptText == null){ - Platform.runLater(()-> animateFLoatingLabel(up)); - }else{ - if(transition!=null){ - transition.stop(); - transition.getChildren().remove(promptTextUpTransition); - transition = null; - } - if(up && promptContainer.getTranslateY() == 0){ - promptTextDownTransition.stop(); - promptTextUpTransition.play(); - }else if(!up){ - promptTextUpTransition.stop(); - promptTextDownTransition.play(); - } - } - } - - private boolean usePromptText() { - String txt = getSkinnable().getText(); - String promptTxt = getSkinnable().getPromptText(); - boolean hasPromptText = (txt == null || txt.isEmpty()) && promptTxt != null && !promptTxt.isEmpty() && !promptTextFill.get().equals(Color.TRANSPARENT); - return hasPromptText; - } - - private void showError(ValidatorBase validator){ - // set text in error label - errorLabel.setText(validator.getMessage()); - // show error icon - Node awsomeIcon = validator.getIcon(); - errorIcon.getChildren().clear(); - if(awsomeIcon!=null){ - errorIcon.getChildren().add(awsomeIcon); - StackPane.setAlignment(awsomeIcon, Pos.TOP_RIGHT); - } - // init only once, to fix the text pane from resizing - if(initYLayout == -1){ - scrollPane.setMaxHeight(scrollPane.getHeight()); - initYLayout = scrollPane.getBoundsInParent().getMinY(); - initHeight = getSkinnable().getHeight(); - currentFieldHeight = initHeight; - } - errorContainer.setVisible(true); - errorShown = true; - } - - private void hideError(){ - if(heightChanged){ - new Timeline(new KeyFrame(Duration.millis(160), new KeyValue(scrollPane.translateYProperty(), 0, Interpolator.EASE_BOTH))).play(); - // reset the height of text field - new Timeline(new KeyFrame(Duration.millis(160), new KeyValue(getSkinnable().minHeightProperty(), initHeight, Interpolator.EASE_BOTH))).play(); - heightChanged = false; - } - // clear error label text - errorLabel.setText(null); - oldErrorLabelHeight = errorLabelInitHeight; - // clear error icon - errorIcon.getChildren().clear(); - // reset the height of the text field - currentFieldHeight = initHeight; - // hide error container - errorContainer.setVisible(false); - errorShown = false; - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.jfoenix.android.skins; + +import com.jfoenix.concurrency.JFXUtilities; +import com.jfoenix.controls.JFXTextArea; +import com.jfoenix.transitions.CachedTransition; +import com.jfoenix.validation.base.ValidatorBase; +import com.sun.javafx.scene.control.skin.TextAreaSkin; +import com.sun.javafx.scene.control.skin.TextAreaSkinAndroid; +import javafx.animation.Animation.Status; +import javafx.animation.*; +import javafx.application.Platform; +import javafx.beans.binding.Bindings; +import javafx.beans.binding.BooleanBinding; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.*; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; +import javafx.scene.text.Text; +import javafx.scene.transform.Scale; +import javafx.util.Duration; + +import java.lang.reflect.Field; + +/** + *
+ * Note: the implementation is a copy of the original {@link com.jfoenix.skins.JFXTextAreaSkin JFXTextAreaSkin} + * however it extends the JavaFXPorts text area android skin. + * + * @author Shadi Shaheen + * @version 2.0 + * @since 2017-01-25 + */ +public class JFXTextAreaSkinAndroid extends TextAreaSkinAndroid { + + private static Background transparentBackground = new Background( + new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY), + new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY), + new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY), + new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY)); + + private boolean invalid = true; + + private StackPane line = new StackPane(); + private StackPane focusedLine = new StackPane(); + + private Label errorLabel = new Label(); + private StackPane errorIcon = new StackPane(); + private HBox errorContainer; + private ScrollPane scrollPane; + + private double initScale = 0.05; + private double oldErrorLabelHeight = -1; + // private Region textPane; + private double initYLayout = -1; + private double initHeight = -1; + private boolean errorShown = false; + private double currentFieldHeight = -1; + private double errorLabelInitHeight = 0; + private boolean heightChanged = false; + + private Pane promptContainer; + private Text promptText; + + private CachedTransition promptTextUpTransition; + private CachedTransition promptTextDownTransition; + private CachedTransition promptTextColorTransition; + + private Timeline hideErrorAnimation; + private ParallelTransition transition; + + private Scale promptTextScale = new Scale(1,1,0,0); + private Scale scale = new Scale(initScale,1); + private Timeline linesAnimation = new Timeline( + new KeyFrame(Duration.ZERO, + new KeyValue(scale.xProperty(), initScale, Interpolator.EASE_BOTH), + new KeyValue(focusedLine.opacityProperty(), 0, Interpolator.EASE_BOTH)), + new KeyFrame(Duration.millis(1), + new KeyValue(focusedLine.opacityProperty(), 1, Interpolator.EASE_BOTH)), + new KeyFrame(Duration.millis(160), + new KeyValue(scale.xProperty(), 1, Interpolator.EASE_BOTH)) + ); + + private Paint oldPromptTextFill; + private BooleanBinding usePromptText = Bindings.createBooleanBinding(()-> usePromptText(), getSkinnable().textProperty(), getSkinnable().promptTextProperty()); + + public JFXTextAreaSkinAndroid(JFXTextArea textArea) { + super(textArea); + // init text area properties + scrollPane = (ScrollPane) getChildren().get(0); + ((Region)scrollPane.getContent()).setPadding(new Insets(0)); + // hide text area borders + scrollPane.setBackground(transparentBackground); + ((Region)scrollPane.getContent()).setBackground(transparentBackground); + getSkinnable().setBackground(transparentBackground); + textArea.setWrapText(true); + + errorLabel.getStyleClass().add("error-label"); + errorLabel.setPadding(new Insets(4,0,0,0)); + errorLabel.setWrapText(true); + errorIcon.setTranslateY(3); + StackPane errorLabelContainer = new StackPane(); + errorLabelContainer.getChildren().add(errorLabel); + StackPane.setAlignment(errorLabel, Pos.CENTER_LEFT); + + promptContainer = new StackPane(); + + line.getStyleClass().add("input-line"); + focusedLine.getStyleClass().add("input-focused-line"); + // draw lines + line.setPrefHeight(1); + line.setTranslateY(1 + 4 + 2); // translate = prefHeight + init_translation + line.setBackground(new Background(new BackgroundFill(((JFXTextArea)getSkinnable()).getUnFocusColor(), + CornerRadii.EMPTY, Insets.EMPTY))); + if(getSkinnable().isDisabled()) { + line.setBorder(new Border(new BorderStroke(((JFXTextArea) getSkinnable()).getUnFocusColor(), + BorderStrokeStyle.DASHED, CornerRadii.EMPTY, new BorderWidths(1)))); + line.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, + CornerRadii.EMPTY, Insets.EMPTY))); + } + + // focused line + focusedLine.setPrefHeight(2); + focusedLine.setTranslateY(0 + 4 + 2); // translate = prefHeight + init_translation(-1) + focusedLine.setBackground(new Background(new BackgroundFill(((JFXTextArea)getSkinnable()).getFocusColor(), + CornerRadii.EMPTY, Insets.EMPTY))); + focusedLine.setOpacity(0); + focusedLine.getTransforms().add(scale); + + errorContainer = new HBox(); + errorContainer.getChildren().setAll(errorLabelContainer, errorIcon); + HBox.setHgrow(errorLabelContainer, Priority.ALWAYS); + + errorContainer.setSpacing(10); + errorContainer.setVisible(false); + errorContainer.setOpacity(0); + + getChildren().addAll(line, focusedLine, promptContainer, errorContainer); + + getSkinnable().setBackground(transparentBackground); + + // errorContainer.layoutXProperty().bind(scrollPane.layoutXProperty()); + // errorContainer.layoutYProperty().bind(scrollPane.layoutYProperty()); + + + // add listeners to show error label + errorLabel.heightProperty().addListener((o,oldVal,newVal)->{ + if(errorShown){ + if(oldErrorLabelHeight == -1) + oldErrorLabelHeight = errorLabelInitHeight = oldVal.doubleValue(); + + heightChanged = true; + double newHeight = this.getSkinnable().getHeight() - oldErrorLabelHeight + newVal.doubleValue(); + // // show the error + // Timeline errorAnimation = new Timeline( + // new KeyFrame(Duration.ZERO, new KeyValue(getSkinnable().minHeightProperty(), currentFieldHeight, Interpolator.EASE_BOTH)), + // new KeyFrame(Duration.millis(160), + // // text pane animation + // new KeyValue(mainPane.translateYProperty(), (initYlayout + mainPane.getMaxHeight()/2) - newHeight/2, Interpolator.EASE_BOTH), + // // animate the height change effect + // new KeyValue(getSkinnable().minHeightProperty(), newHeight, Interpolator.EASE_BOTH))); + // errorAnimation.play(); + // // show the error label when finished + // errorAnimation.setOnFinished(finish->new Timeline(new KeyFrame(Duration.millis(160),new KeyValue(errorContainer.opacityProperty(), 1, Interpolator.EASE_BOTH))).play()); + currentFieldHeight = newHeight; + oldErrorLabelHeight = newVal.doubleValue(); + } + }); + errorContainer.visibleProperty().addListener((o,oldVal,newVal)->{ + // show the error label if it's not shown + if(newVal) new Timeline(new KeyFrame(Duration.millis(160),new KeyValue(errorContainer.opacityProperty(), 1, Interpolator.EASE_BOTH))).play(); + }); + + + textArea.labelFloatProperty().addListener((o,oldVal,newVal)->{ + if(newVal) JFXUtilities.runInFX(()->createFloatingLabel()); + else promptText.visibleProperty().bind(usePromptText); + createFocusTransition(); + }); + + textArea.activeValidatorProperty().addListener((o,oldVal,newVal)->{ + if(scrollPane != null){ + if(!((JFXTextArea)getSkinnable()).isDisableAnimation()){ + if(hideErrorAnimation!=null && hideErrorAnimation.getStatus().equals(Status.RUNNING)) + hideErrorAnimation.stop(); + if(newVal!=null){ + hideErrorAnimation = new Timeline(new KeyFrame(Duration.millis(160),new KeyValue(errorContainer.opacityProperty(), 0, Interpolator.EASE_BOTH))); + hideErrorAnimation.setOnFinished(finish->{ + errorContainer.setVisible(false); + JFXUtilities.runInFX(()->showError(newVal)); + }); + hideErrorAnimation.play(); + }else{ + JFXUtilities.runInFX(()->hideError()); + } + }else{ + if(newVal!=null) JFXUtilities.runInFXAndWait(()->showError(newVal)); + else JFXUtilities.runInFXAndWait(()->hideError()); + } + } + }); + + textArea.focusColorProperty().addListener((o,oldVal,newVal)->{ + if(newVal!=null) { + focusedLine.setBackground(new Background(new BackgroundFill(newVal, CornerRadii.EMPTY, Insets.EMPTY))); + if(((JFXTextArea)getSkinnable()).isLabelFloat()){ + promptTextColorTransition = new CachedTransition(promptContainer, new Timeline( + new KeyFrame(Duration.millis(1300),new KeyValue(promptTextFill, newVal, Interpolator.EASE_BOTH)))) + { + {setDelay(Duration.millis(0)); setCycleDuration(Duration.millis(160));} + protected void starting() {super.starting(); oldPromptTextFill = promptTextFill.get();} + }; + // reset transition + transition = null; + } + } + }); + textArea.unFocusColorProperty().addListener((o,oldVal,newVal)->{ + if(newVal!=null) + line.setBackground(new Background(new BackgroundFill(newVal, CornerRadii.EMPTY, Insets.EMPTY))); + }); + + // handle animation on focus gained/lost event + textArea.focusedProperty().addListener((o,oldVal,newVal) -> { + if (newVal) focus(); + else unFocus(); + }); + + // handle text changing at runtime + textArea.textProperty().addListener((o,oldVal,newVal)->{ + if(!getSkinnable().isFocused() && ((JFXTextArea)getSkinnable()).isLabelFloat()){ + if(newVal == null || newVal.isEmpty()) animateFLoatingLabel(false); + else animateFLoatingLabel(true); + } + }); + + textArea.backgroundProperty().addListener((o,oldVal,newVal)->{ + // Force transparent background + if(oldVal == transparentBackground && newVal != transparentBackground){ + textArea.setBackground(transparentBackground); + } + }); + + textArea.disabledProperty().addListener((o,oldVal,newVal) -> { + line.setBorder(newVal ? new Border(new BorderStroke(((JFXTextArea)getSkinnable()).getUnFocusColor(), + BorderStrokeStyle.DASHED, CornerRadii.EMPTY, new BorderWidths(line.getHeight()))) : Border.EMPTY); + line.setBackground(new Background(new BackgroundFill( newVal? Color.TRANSPARENT : ((JFXTextArea)getSkinnable()).getUnFocusColor(), + CornerRadii.EMPTY, Insets.EMPTY))); + }); + + // prevent setting prompt text fill to transparent when text field is focused (override java transparent color if the control was focused) + promptTextFill.addListener((o,oldVal,newVal)->{ + if(Color.TRANSPARENT.equals(newVal) && ((JFXTextArea)getSkinnable()).isLabelFloat()){ + promptTextFill.set(oldVal); + } + }); + } + + + @Override + protected void layoutChildren(final double x, final double y, final double w, final double h) { + super.layoutChildren(x, y, w, h); + + // change control properties if and only if animations are stopped + if((transition == null || transition.getStatus().equals(Status.STOPPED))){ + if(getSkinnable().isFocused() && ((JFXTextArea)getSkinnable()).isLabelFloat()){ + promptTextFill.set(((JFXTextArea)getSkinnable()).getFocusColor()); + } + } + + if(invalid){ + invalid = false; +// // set the default background of text area viewport to white + Region viewPort = ((Region)scrollPane.getChildrenUnmodifiable().get(0)); + viewPort.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY))); + // reapply css of scroll pane in case set by the user + viewPort.applyCss(); + +// errorLabel.maxWidthProperty().bind(Bindings.createDoubleBinding(()->getSkinnable().getWidth()/1.14, getSkinnable().widthProperty())); + + // create floating label + createFloatingLabel(); + // to position the prompt node properly + super.layoutChildren(x, y, w, h); + // update validation container + if(((JFXTextArea)getSkinnable()).getActiveValidator()!=null) updateValidationError(); + // focus + createFocusTransition(); + if(getSkinnable().isFocused()) focus(); + } + + focusedLine.resizeRelocate(x, h-focusedLine.prefHeight(-1), w, focusedLine.prefHeight(-1)); + line.resizeRelocate(x, h-focusedLine.prefHeight(-1), w, line.prefHeight(-1)); + errorContainer.resizeRelocate(x, y, w, -1); + errorContainer.setTranslateY(h + focusedLine.getHeight() + 4); + scale.setPivotX(w/2); + } + + private void updateValidationError() { + if(hideErrorAnimation!=null && hideErrorAnimation.getStatus().equals(Status.RUNNING)) + hideErrorAnimation.stop(); + hideErrorAnimation = new Timeline( + new KeyFrame(Duration.millis(160), + new KeyValue(errorContainer.opacityProperty(), 0, Interpolator.EASE_BOTH))); + hideErrorAnimation.setOnFinished(finish->{ + errorContainer.setVisible(false); + showError(((JFXTextArea)getSkinnable()).getActiveValidator()); + }); + hideErrorAnimation.play(); + } + + private void createFloatingLabel() { + if(((JFXTextArea)getSkinnable()).isLabelFloat()){ + if(promptText == null){ + // get the prompt text node or create it + boolean triggerFloatLabel = false; + if(((Region)scrollPane.getContent()).getChildrenUnmodifiable().get(0) instanceof Text) promptText = (Text) ((Region)scrollPane.getContent()).getChildrenUnmodifiable().get(0); + else{ + Field field; + try { + field = TextAreaSkin.class.getDeclaredField("promptNode"); + field.setAccessible(true); + createPromptNode(); + field.set(this, promptText); + // position the prompt node in its position + triggerFloatLabel = true; + oldPromptTextFill = promptTextFill.get(); + } catch (NoSuchFieldException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (SecurityException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalAccessException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + // fixed issue text area is being resized when the content is excedeing its width + promptText.wrappingWidthProperty().addListener((o,oldval,newVal)->{ + if(newVal.doubleValue() > getSkinnable().getWidth()) + promptText.setWrappingWidth(getSkinnable().getWidth()); + }); + + promptText.getTransforms().add(promptTextScale); + promptContainer.getChildren().add(promptText); + if(triggerFloatLabel){ + promptText.setTranslateY(-promptText.getBoundsInLocal().getHeight()-2); + promptTextScale.setX(0.85); + promptTextScale.setY(0.85); + } + } + + // create prompt animations + promptTextUpTransition = new CachedTransition(promptContainer, new Timeline( + new KeyFrame(Duration.millis(1300), + new KeyValue(promptText.translateYProperty(), -promptText.getLayoutBounds().getHeight()-2, Interpolator.EASE_BOTH), + new KeyValue(promptTextScale.xProperty(), 0.85 , Interpolator.EASE_BOTH), + new KeyValue(promptTextScale.yProperty(), 0.85 , Interpolator.EASE_BOTH)))){{ setDelay(Duration.millis(0)); setCycleDuration(Duration.millis(240)); }}; + + promptTextColorTransition = new CachedTransition(promptContainer, new Timeline( + new KeyFrame(Duration.millis(1300),new KeyValue(promptTextFill, ((JFXTextArea)getSkinnable()).getFocusColor(), Interpolator.EASE_BOTH)))) + {{ setDelay(Duration.millis(0)); setCycleDuration(Duration.millis(160)); } + protected void starting() {super.starting(); oldPromptTextFill = promptTextFill.get();};}; + + promptTextDownTransition = new CachedTransition(promptContainer, new Timeline( + new KeyFrame(Duration.millis(1300), + new KeyValue(promptText.translateYProperty(), 0, Interpolator.EASE_BOTH), + new KeyValue(promptTextScale.xProperty(),1 , Interpolator.EASE_BOTH), + new KeyValue(promptTextScale.yProperty(),1 , Interpolator.EASE_BOTH)) + )){{ setDelay(Duration.millis(0)); setCycleDuration(Duration.millis(240)); }}; + promptTextDownTransition.setOnFinished((finish)->{ + promptText.setTranslateY(0); + promptTextScale.setX(1); + promptTextScale.setY(1); + }); + + promptText.visibleProperty().unbind(); + promptText.visibleProperty().set(true); + } + } + + private void createPromptNode(){ + promptText = new Text(); + promptText.setManaged(false); + promptText.getStyleClass().add("text"); + promptText.visibleProperty().bind(usePromptText); + promptText.fontProperty().bind(getSkinnable().fontProperty()); + promptText.textProperty().bind(getSkinnable().promptTextProperty()); + promptText.fillProperty().bind(promptTextFill); + promptText.setLayoutX(1); + } + + private void focus(){ + /* + * in case the method request layout is not called before focused + * this is bug is reported while editing treetableview cells + */ + if(scrollPane == null){ + Platform.runLater(()->focus()); + }else{ + // create the focus animations + if(transition == null) createFocusTransition(); + transition.play(); + } + } + + private void createFocusTransition() { + transition = new ParallelTransition(); + if(((JFXTextArea)getSkinnable()).isLabelFloat()){ + transition.getChildren().add(promptTextUpTransition); + transition.getChildren().add(promptTextColorTransition); + } + transition.getChildren().add(linesAnimation); + } + + private void unFocus() { + if(transition!=null) transition.stop(); + scale.setX(initScale); + focusedLine.setOpacity(0); + if(((JFXTextArea)getSkinnable()).isLabelFloat() && oldPromptTextFill != null){ + promptTextFill.set(oldPromptTextFill); + if(usePromptText()) promptTextDownTransition.play(); + } + } + + /** + * this method is called when the text property is changed when the + * field is not focused (changed in code) + * @param up + */ + private void animateFLoatingLabel(boolean up){ + if(promptText == null){ + Platform.runLater(()-> animateFLoatingLabel(up)); + }else{ + if(transition!=null){ + transition.stop(); + transition.getChildren().remove(promptTextUpTransition); + transition = null; + } + if(up && promptContainer.getTranslateY() == 0){ + promptTextDownTransition.stop(); + promptTextUpTransition.play(); + }else if(!up){ + promptTextUpTransition.stop(); + promptTextDownTransition.play(); + } + } + } + + private boolean usePromptText() { + String txt = getSkinnable().getText(); + String promptTxt = getSkinnable().getPromptText(); + boolean hasPromptText = (txt == null || txt.isEmpty()) && promptTxt != null && !promptTxt.isEmpty() && !promptTextFill.get().equals(Color.TRANSPARENT); + return hasPromptText; + } + + private void showError(ValidatorBase validator){ + // set text in error label + errorLabel.setText(validator.getMessage()); + // show error icon + Node awsomeIcon = validator.getIcon(); + errorIcon.getChildren().clear(); + if(awsomeIcon!=null){ + errorIcon.getChildren().add(awsomeIcon); + StackPane.setAlignment(awsomeIcon, Pos.TOP_RIGHT); + } + // init only once, to fix the text pane from resizing + if(initYLayout == -1){ + scrollPane.setMaxHeight(scrollPane.getHeight()); + initYLayout = scrollPane.getBoundsInParent().getMinY(); + initHeight = getSkinnable().getHeight(); + currentFieldHeight = initHeight; + } + errorContainer.setVisible(true); + errorShown = true; + } + + private void hideError(){ + if(heightChanged){ + new Timeline(new KeyFrame(Duration.millis(160), new KeyValue(scrollPane.translateYProperty(), 0, Interpolator.EASE_BOTH))).play(); + // reset the height of text field + new Timeline(new KeyFrame(Duration.millis(160), new KeyValue(getSkinnable().minHeightProperty(), initHeight, Interpolator.EASE_BOTH))).play(); + heightChanged = false; + } + // clear error label text + errorLabel.setText(null); + oldErrorLabelHeight = errorLabelInitHeight; + // clear error icon + errorIcon.getChildren().clear(); + // reset the height of the text field + currentFieldHeight = initHeight; + // hide error container + errorContainer.setVisible(false); + errorShown = false; + } +} diff --git a/src/com/jfoenix/android/skins/JFXTextFieldSkinAndroid.java b/jfoenix/src/main/java/com/jfoenix/android/skins/JFXTextFieldSkinAndroid.java similarity index 97% rename from src/com/jfoenix/android/skins/JFXTextFieldSkinAndroid.java rename to jfoenix/src/main/java/com/jfoenix/android/skins/JFXTextFieldSkinAndroid.java index e8dbecb6..7777ebc7 100644 --- a/src/com/jfoenix/android/skins/JFXTextFieldSkinAndroid.java +++ b/jfoenix/src/main/java/com/jfoenix/android/skins/JFXTextFieldSkinAndroid.java @@ -1,489 +1,489 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package com.jfoenix.android.skins; - -import com.jfoenix.concurrency.JFXUtilities; -import com.jfoenix.controls.JFXTextField; -import com.jfoenix.skins.JFXTextFieldSkin; -import com.jfoenix.transitions.CachedTransition; -import com.jfoenix.validation.base.ValidatorBase; -import com.sun.javafx.scene.control.skin.TextFieldSkin; -import com.sun.javafx.scene.control.skin.TextFieldSkinAndroid; -import javafx.animation.Animation.Status; -import javafx.animation.*; -import javafx.application.Platform; -import javafx.beans.binding.Bindings; -import javafx.beans.binding.BooleanBinding; -import javafx.geometry.Insets; -import javafx.geometry.Pos; -import javafx.scene.Node; -import javafx.scene.control.Label; -import javafx.scene.layout.*; -import javafx.scene.paint.Color; -import javafx.scene.paint.Paint; -import javafx.scene.text.Text; -import javafx.scene.transform.Scale; -import javafx.util.Duration; - -import java.lang.reflect.Field; - -/** - *
- * Note: the implementation is a copy of the original {@link JFXTextFieldSkin} - * however it extends the JavaFXPorts text field android skin. - * - * @author Shadi Shaheen - * @version 2.0 - * @since 2017-01-25 - */ -public class JFXTextFieldSkinAndroid extends TextFieldSkinAndroid{ - - private boolean invalid = true; - - private StackPane line = new StackPane(); - private StackPane focusedLine = new StackPane(); - - private Label errorLabel = new Label(); - private StackPane errorIcon = new StackPane(); - private HBox errorContainer; - private Pane textPane; - - private double initScale = 0.05; - private double oldErrorLabelHeight = -1; - private double initYLayout = -1; - private double initHeight = -1; - private boolean errorShown = false; - private double currentFieldHeight = -1; - private double errorLabelInitHeight = 0; - - private boolean heightChanged = false; - private StackPane promptContainer; - private Text promptText; - - private ParallelTransition transition; - private Timeline hideErrorAnimation; - private CachedTransition promptTextUpTransition; - private CachedTransition promptTextDownTransition; - private CachedTransition promptTextColorTransition; - - private Scale promptTextScale = new Scale(1,1,0,0); - private Scale scale = new Scale(initScale,1); - private Timeline linesAnimation = new Timeline( - new KeyFrame(Duration.ZERO, - new KeyValue(scale.xProperty(), initScale, Interpolator.EASE_BOTH), - new KeyValue(focusedLine.opacityProperty(), 0, Interpolator.EASE_BOTH)), - new KeyFrame(Duration.millis(1), - new KeyValue(focusedLine.opacityProperty(), 1, Interpolator.EASE_BOTH)), - new KeyFrame(Duration.millis(160), - new KeyValue(scale.xProperty(), 1, Interpolator.EASE_BOTH)) - ); - - private Paint oldPromptTextFill; - private BooleanBinding usePromptText = Bindings.createBooleanBinding(()-> usePromptText(), getSkinnable().textProperty(), getSkinnable().promptTextProperty()); - - public JFXTextFieldSkinAndroid(JFXTextField field) { - super(field); - // initial styles - field.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, null, null))); - field.setPadding(new Insets(4,0,4,0)); - - errorLabel.getStyleClass().add("error-label"); - errorLabel.setPadding(new Insets(4,0,0,0)); - errorLabel.setWrapText(true); - errorIcon.setTranslateY(3); - - StackPane errorLabelContainer = new StackPane(); - errorLabelContainer.getChildren().add(errorLabel); - StackPane.setAlignment(errorLabel, Pos.CENTER_LEFT); - - line.getStyleClass().add("input-line"); - getChildren().add(line); - focusedLine.getStyleClass().add("input-focused-line"); - getChildren().add(focusedLine); - - // draw lines - line.setPrefHeight(1); - line.setTranslateY(1); // translate = prefHeight + init_translation - line.setBackground(new Background(new BackgroundFill(((JFXTextField)getSkinnable()).getUnFocusColor(), - CornerRadii.EMPTY, Insets.EMPTY))); - if(getSkinnable().isDisabled()) { - line.setBorder(new Border(new BorderStroke(((JFXTextField) getSkinnable()).getUnFocusColor(), - BorderStrokeStyle.DASHED, CornerRadii.EMPTY, new BorderWidths(1)))); - line.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, - CornerRadii.EMPTY, Insets.EMPTY))); - } - - // focused line - focusedLine.setPrefHeight(2); - focusedLine.setTranslateY(0); // translate = prefHeight + init_translation(-1) - focusedLine.setBackground(new Background(new BackgroundFill(((JFXTextField)getSkinnable()).getFocusColor(), - CornerRadii.EMPTY, Insets.EMPTY))); - focusedLine.setOpacity(0); - focusedLine.getTransforms().add(scale); - - - promptContainer = new StackPane(); - getChildren().add(promptContainer); - - errorContainer = new HBox(); - errorContainer.getChildren().setAll(errorLabelContainer, errorIcon); - HBox.setHgrow(errorLabelContainer, Priority.ALWAYS); - errorContainer.setSpacing(10); - errorContainer.setVisible(false); - errorContainer.setOpacity(0); - getChildren().add(errorContainer); - - // add listeners to show error label - errorLabel.heightProperty().addListener((o,oldVal,newVal)->{ - if(errorShown){ - if(oldErrorLabelHeight == -1) - oldErrorLabelHeight = errorLabelInitHeight = oldVal.doubleValue(); - heightChanged = true; - double newHeight = this.getSkinnable().getHeight() - oldErrorLabelHeight + newVal.doubleValue(); - // show the error - Timeline errorAnimation = new Timeline( - new KeyFrame(Duration.ZERO, new KeyValue(getSkinnable().minHeightProperty(), currentFieldHeight, Interpolator.EASE_BOTH)), - new KeyFrame(Duration.millis(160), - // text pane animation - new KeyValue(textPane.translateYProperty(), (initYLayout + textPane.getMaxHeight()/2) - newHeight/2, Interpolator.EASE_BOTH), - // animate the height change effect - new KeyValue(getSkinnable().minHeightProperty(), newHeight, Interpolator.EASE_BOTH))); - errorAnimation.play(); - // show the error label when finished - errorAnimation.setOnFinished(finish->new Timeline(new KeyFrame(Duration.millis(160),new KeyValue(errorContainer.opacityProperty(), 1, Interpolator.EASE_BOTH))).play()); - currentFieldHeight = newHeight; - oldErrorLabelHeight = newVal.doubleValue(); - } - }); - errorContainer.visibleProperty().addListener((o,oldVal,newVal)->{ - // show the error label if it's not shown - if(newVal) new Timeline(new KeyFrame(Duration.millis(160),new KeyValue(errorContainer.opacityProperty(), 1, Interpolator.EASE_BOTH))).play(); - }); - - - field.labelFloatProperty().addListener((o,oldVal,newVal)->{ - if(newVal) JFXUtilities.runInFX(()->createFloatingLabel()); - else promptText.visibleProperty().bind(usePromptText); - createFocusTransition(); - }); - - field.activeValidatorProperty().addListener((o,oldVal,newVal)->{ - if(textPane != null){ - if(!((JFXTextField)getSkinnable()).isDisableAnimation()){ - if(hideErrorAnimation!=null && hideErrorAnimation.getStatus().equals(Status.RUNNING)) - hideErrorAnimation.stop(); - if(newVal!=null){ - hideErrorAnimation = new Timeline(new KeyFrame(Duration.millis(160),new KeyValue(errorContainer.opacityProperty(), 0, Interpolator.EASE_BOTH))); - hideErrorAnimation.setOnFinished(finish->{ - errorContainer.setVisible(false); - JFXUtilities.runInFX(()->showError(newVal)); - }); - hideErrorAnimation.play(); - }else{ - JFXUtilities.runInFX(()->hideError()); - } - }else{ - if(newVal!=null) JFXUtilities.runInFXAndWait(()->showError(newVal)); - else JFXUtilities.runInFXAndWait(()->hideError()); - } - } - }); - - field.focusColorProperty().addListener((o,oldVal,newVal)->{ - if(newVal!=null) { - focusedLine.setBackground(new Background(new BackgroundFill(newVal, CornerRadii.EMPTY, Insets.EMPTY))); - if(((JFXTextField)getSkinnable()).isLabelFloat()){ - promptTextColorTransition = new CachedTransition(textPane, new Timeline( - new KeyFrame(Duration.millis(1300), - new KeyValue(promptTextFill, newVal, Interpolator.EASE_BOTH)))) - { - {setDelay(Duration.millis(0)); setCycleDuration(Duration.millis(160));} - protected void starting() {super.starting(); oldPromptTextFill = promptTextFill.get();} - }; - // reset transition - transition = null; - } - } - }); - field.unFocusColorProperty().addListener((o,oldVal,newVal)->{ - if(newVal!=null) - line.setBackground(new Background(new BackgroundFill(newVal, CornerRadii.EMPTY, Insets.EMPTY))); - }); - - // handle animation on focus gained/lost event - field.focusedProperty().addListener((o,oldVal,newVal) -> { - if (newVal) focus(); - else unFocus(); - }); - - // handle text changing at runtime - field.textProperty().addListener((o,oldVal,newVal)->{ - if(!getSkinnable().isFocused() && ((JFXTextField)getSkinnable()).isLabelFloat()){ - if(newVal == null || newVal.isEmpty()) animateFloatingLabel(false); - else animateFloatingLabel(true); - } - }); - - field.disabledProperty().addListener((o,oldVal,newVal) -> { - line.setBorder(newVal ? new Border(new BorderStroke(((JFXTextField)getSkinnable()).getUnFocusColor(), - BorderStrokeStyle.DASHED, CornerRadii.EMPTY, new BorderWidths(line.getHeight()))) : Border.EMPTY); - line.setBackground(new Background(new BackgroundFill( newVal? Color.TRANSPARENT : ((JFXTextField)getSkinnable()).getUnFocusColor(), - CornerRadii.EMPTY, Insets.EMPTY))); - }); - - // prevent setting prompt text fill to transparent when text field is focused (override java transparent color if the control was focused) - promptTextFill.addListener((o,oldVal,newVal)->{ - if(Color.TRANSPARENT.equals(newVal) && ((JFXTextField)getSkinnable()).isLabelFloat()) - promptTextFill.set(oldVal); - }); - - } - - @Override - protected void layoutChildren(final double x, final double y, final double w, final double h) { - super.layoutChildren(x, y, w, h); - - // change control properties if and only if animations are stopped - if((transition == null || transition.getStatus().equals(Status.STOPPED))){ - if(getSkinnable().isFocused() && ((JFXTextField)getSkinnable()).isLabelFloat()){ - promptTextFill.set(((JFXTextField)getSkinnable()).getFocusColor()); - } - } - - if(invalid){ - invalid = false; - textPane = ((Pane)this.getChildren().get(0)); - // create floating label - createFloatingLabel(); - // to position the prompt node properly - super.layoutChildren(x, y, w, h); - // update validation container - if(((JFXTextField)getSkinnable()).getActiveValidator()!=null) updateValidationError(); - // focus - createFocusTransition(); - if(getSkinnable().isFocused()) focus(); - } - - focusedLine.resizeRelocate(x, getSkinnable().getHeight(), w, focusedLine.prefHeight(-1)); - line.resizeRelocate(x, getSkinnable().getHeight(), w, line.prefHeight(-1)); - errorContainer.relocate(x, getSkinnable().getHeight() + focusedLine.getHeight()); - scale.setPivotX(w/2); - } - - private void updateValidationError() { - if(hideErrorAnimation!=null && hideErrorAnimation.getStatus().equals(Status.RUNNING)) - hideErrorAnimation.stop(); - hideErrorAnimation = new Timeline( - new KeyFrame(Duration.millis(160), - new KeyValue(errorContainer.opacityProperty(), 0, Interpolator.EASE_BOTH))); - hideErrorAnimation.setOnFinished(finish->{ - errorContainer.setVisible(false); - showError(((JFXTextField)getSkinnable()).getActiveValidator()); - }); - hideErrorAnimation.play(); - } - - - private void createFloatingLabel() { - if(((JFXTextField)getSkinnable()).isLabelFloat()){ - if(promptText == null){ - // get the prompt text node or create it - boolean triggerFloatLabel = false; - if(textPane.getChildren().get(0) instanceof Text) promptText = (Text) textPane.getChildren().get(0); - else{ - Field field; - try { - field = TextFieldSkin.class.getDeclaredField("promptNode"); - field.setAccessible(true); - createPromptNode(); - field.set(this, promptText); - // position the prompt node in its position - triggerFloatLabel = true; - } catch (NoSuchFieldException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (SecurityException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IllegalArgumentException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IllegalAccessException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - promptText.getTransforms().add(promptTextScale); - promptContainer.getChildren().add(promptText); - - if(triggerFloatLabel){ - promptText.setTranslateY(-textPane.getHeight()); - promptTextScale.setX(0.85); - promptTextScale.setY(0.85); - } - } - - promptTextUpTransition = new CachedTransition(textPane, new Timeline( - new KeyFrame(Duration.millis(1300), - new KeyValue(promptText.translateYProperty(), -textPane.getHeight(), Interpolator.EASE_BOTH), - new KeyValue(promptTextScale.xProperty(), 0.85 , Interpolator.EASE_BOTH), - new KeyValue(promptTextScale.yProperty(), 0.85 , Interpolator.EASE_BOTH)))){{ setDelay(Duration.millis(0)); setCycleDuration(Duration.millis(240)); }}; - - promptTextColorTransition = new CachedTransition(textPane, new Timeline( - new KeyFrame(Duration.millis(1300), - new KeyValue(promptTextFill, ((JFXTextField)getSkinnable()).getFocusColor(), Interpolator.EASE_BOTH)))) - { - { setDelay(Duration.millis(0)); setCycleDuration(Duration.millis(160)); } - protected void starting() {super.starting(); oldPromptTextFill = promptTextFill.get();}; - }; - - promptTextDownTransition = new CachedTransition(textPane, new Timeline( - new KeyFrame(Duration.millis(1300), - new KeyValue(promptText.translateYProperty(), 0, Interpolator.EASE_BOTH), - new KeyValue(promptTextScale.xProperty(), 1 , Interpolator.EASE_BOTH), - new KeyValue(promptTextScale.yProperty(), 1 , Interpolator.EASE_BOTH)))) - {{ setDelay(Duration.millis(0)); setCycleDuration(Duration.millis(240));}}; - promptTextDownTransition.setOnFinished((finish)->{ - promptText.setTranslateY(0); - promptTextScale.setX(1); - promptTextScale.setY(1); - }); - promptText.visibleProperty().unbind(); - promptText.visibleProperty().set(true); - } - } - - private void createPromptNode(){ - promptText = new Text(); - promptText.setManaged(false); - promptText.getStyleClass().add("text"); - promptText.visibleProperty().bind(usePromptText); - promptText.fontProperty().bind(getSkinnable().fontProperty()); - promptText.textProperty().bind(getSkinnable().promptTextProperty()); - promptText.fillProperty().bind(promptTextFill); - promptText.setLayoutX(1); - } - - private void focus(){ - /* - * in case the method request layout is not called before focused - * this is bug is reported while editing TreeTableView cells - */ - if(textPane == null){ - Platform.runLater(()->focus()); - }else{ - // create the focus animations - if(transition == null) createFocusTransition(); - transition.play(); - } - } - - private void createFocusTransition() { - transition = new ParallelTransition(); - if(((JFXTextField)getSkinnable()).isLabelFloat()){ - transition.getChildren().add(promptTextUpTransition); - transition.getChildren().add(promptTextColorTransition); - } - transition.getChildren().add(linesAnimation); - } - - private void unFocus() { - if(transition!=null) transition.stop(); - scale.setX(initScale); - focusedLine.setOpacity(0); - if(((JFXTextField)getSkinnable()).isLabelFloat() && oldPromptTextFill != null){ - promptTextFill.set(oldPromptTextFill); - if(usePromptText()) promptTextDownTransition.play(); - } - } - - /** - * this method is called when the text property is changed when the - * field is not focused (changed in code) - * @param up - */ - private void animateFloatingLabel(boolean up){ - if(promptText == null){ - Platform.runLater(()-> animateFloatingLabel(up)); - }else{ - if(transition!=null){ - transition.stop(); - transition.getChildren().remove(promptTextUpTransition); - transition = null; - } - if(up && promptText.getTranslateY() == 0){ - promptTextDownTransition.stop(); - promptTextUpTransition.play(); - }else if(!up){ - promptTextUpTransition.stop(); - promptTextDownTransition.play(); - } - } - } - - private boolean usePromptText() { - String txt = getSkinnable().getText(); - String promptTxt = getSkinnable().getPromptText(); - boolean hasPromptText = (txt == null || txt.isEmpty()) && promptTxt != null && !promptTxt.isEmpty() && !promptTextFill.get().equals(Color.TRANSPARENT); - return hasPromptText; - } - - private void showError(ValidatorBase validator){ - // set text in error label - errorLabel.setText(validator.getMessage()); - // show error icon - Node awsomeIcon = validator.getIcon(); - errorIcon.getChildren().clear(); - if(awsomeIcon!=null){ - errorIcon.getChildren().add(awsomeIcon); - StackPane.setAlignment(awsomeIcon, Pos.TOP_RIGHT); - } - // init only once, to fix the text pane from resizing - if(initYLayout == -1){ - textPane.setMaxHeight(textPane.getHeight()); - initYLayout = textPane.getBoundsInParent().getMinY(); - initHeight = getSkinnable().getHeight(); - currentFieldHeight = initHeight; - } - errorContainer.setVisible(true); - errorShown = true; - } - - private void hideError(){ - if(heightChanged){ - new Timeline(new KeyFrame(Duration.millis(160), new KeyValue(textPane.translateYProperty(), 0, Interpolator.EASE_BOTH))).play(); - // reset the height of text field - new Timeline(new KeyFrame(Duration.millis(160), new KeyValue(getSkinnable().minHeightProperty(), initHeight, Interpolator.EASE_BOTH))).play(); - heightChanged = false; - } - // clear error label text - errorLabel.setText(null); - oldErrorLabelHeight = errorLabelInitHeight; - // clear error icon - errorIcon.getChildren().clear(); - // reset the height of the text field - currentFieldHeight = initHeight; - // hide error container - errorContainer.setVisible(false); - errorShown = false; - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.jfoenix.android.skins; + +import com.jfoenix.concurrency.JFXUtilities; +import com.jfoenix.controls.JFXTextField; +import com.jfoenix.skins.JFXTextFieldSkin; +import com.jfoenix.transitions.CachedTransition; +import com.jfoenix.validation.base.ValidatorBase; +import com.sun.javafx.scene.control.skin.TextFieldSkin; +import com.sun.javafx.scene.control.skin.TextFieldSkinAndroid; +import javafx.animation.Animation.Status; +import javafx.animation.*; +import javafx.application.Platform; +import javafx.beans.binding.Bindings; +import javafx.beans.binding.BooleanBinding; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.layout.*; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; +import javafx.scene.text.Text; +import javafx.scene.transform.Scale; +import javafx.util.Duration; + +import java.lang.reflect.Field; + +/** + *
+ * Note: the implementation is a copy of the original {@link JFXTextFieldSkin}
+ * however it extends the JavaFXPorts text field android skin.
+ *
+ * @author Shadi Shaheen
+ * @version 2.0
+ * @since 2017-01-25
+ */
+public class JFXTextFieldSkinAndroid extends TextFieldSkinAndroid{
+
+ private boolean invalid = true;
+
+ private StackPane line = new StackPane();
+ private StackPane focusedLine = new StackPane();
+
+ private Label errorLabel = new Label();
+ private StackPane errorIcon = new StackPane();
+ private HBox errorContainer;
+ private Pane textPane;
+
+ private double initScale = 0.05;
+ private double oldErrorLabelHeight = -1;
+ private double initYLayout = -1;
+ private double initHeight = -1;
+ private boolean errorShown = false;
+ private double currentFieldHeight = -1;
+ private double errorLabelInitHeight = 0;
+
+ private boolean heightChanged = false;
+ private StackPane promptContainer;
+ private Text promptText;
+
+ private ParallelTransition transition;
+ private Timeline hideErrorAnimation;
+ private CachedTransition promptTextUpTransition;
+ private CachedTransition promptTextDownTransition;
+ private CachedTransition promptTextColorTransition;
+
+ private Scale promptTextScale = new Scale(1,1,0,0);
+ private Scale scale = new Scale(initScale,1);
+ private Timeline linesAnimation = new Timeline(
+ new KeyFrame(Duration.ZERO,
+ new KeyValue(scale.xProperty(), initScale, Interpolator.EASE_BOTH),
+ new KeyValue(focusedLine.opacityProperty(), 0, Interpolator.EASE_BOTH)),
+ new KeyFrame(Duration.millis(1),
+ new KeyValue(focusedLine.opacityProperty(), 1, Interpolator.EASE_BOTH)),
+ new KeyFrame(Duration.millis(160),
+ new KeyValue(scale.xProperty(), 1, Interpolator.EASE_BOTH))
+ );
+
+ private Paint oldPromptTextFill;
+ private BooleanBinding usePromptText = Bindings.createBooleanBinding(()-> usePromptText(), getSkinnable().textProperty(), getSkinnable().promptTextProperty());
+
+ public JFXTextFieldSkinAndroid(JFXTextField field) {
+ super(field);
+ // initial styles
+ field.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, null, null)));
+ field.setPadding(new Insets(4,0,4,0));
+
+ errorLabel.getStyleClass().add("error-label");
+ errorLabel.setPadding(new Insets(4,0,0,0));
+ errorLabel.setWrapText(true);
+ errorIcon.setTranslateY(3);
+
+ StackPane errorLabelContainer = new StackPane();
+ errorLabelContainer.getChildren().add(errorLabel);
+ StackPane.setAlignment(errorLabel, Pos.CENTER_LEFT);
+
+ line.getStyleClass().add("input-line");
+ getChildren().add(line);
+ focusedLine.getStyleClass().add("input-focused-line");
+ getChildren().add(focusedLine);
+
+ // draw lines
+ line.setPrefHeight(1);
+ line.setTranslateY(1); // translate = prefHeight + init_translation
+ line.setBackground(new Background(new BackgroundFill(((JFXTextField)getSkinnable()).getUnFocusColor(),
+ CornerRadii.EMPTY, Insets.EMPTY)));
+ if(getSkinnable().isDisabled()) {
+ line.setBorder(new Border(new BorderStroke(((JFXTextField) getSkinnable()).getUnFocusColor(),
+ BorderStrokeStyle.DASHED, CornerRadii.EMPTY, new BorderWidths(1))));
+ line.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT,
+ CornerRadii.EMPTY, Insets.EMPTY)));
+ }
+
+ // focused line
+ focusedLine.setPrefHeight(2);
+ focusedLine.setTranslateY(0); // translate = prefHeight + init_translation(-1)
+ focusedLine.setBackground(new Background(new BackgroundFill(((JFXTextField)getSkinnable()).getFocusColor(),
+ CornerRadii.EMPTY, Insets.EMPTY)));
+ focusedLine.setOpacity(0);
+ focusedLine.getTransforms().add(scale);
+
+
+ promptContainer = new StackPane();
+ getChildren().add(promptContainer);
+
+ errorContainer = new HBox();
+ errorContainer.getChildren().setAll(errorLabelContainer, errorIcon);
+ HBox.setHgrow(errorLabelContainer, Priority.ALWAYS);
+ errorContainer.setSpacing(10);
+ errorContainer.setVisible(false);
+ errorContainer.setOpacity(0);
+ getChildren().add(errorContainer);
+
+ // add listeners to show error label
+ errorLabel.heightProperty().addListener((o,oldVal,newVal)->{
+ if(errorShown){
+ if(oldErrorLabelHeight == -1)
+ oldErrorLabelHeight = errorLabelInitHeight = oldVal.doubleValue();
+ heightChanged = true;
+ double newHeight = this.getSkinnable().getHeight() - oldErrorLabelHeight + newVal.doubleValue();
+ // show the error
+ Timeline errorAnimation = new Timeline(
+ new KeyFrame(Duration.ZERO, new KeyValue(getSkinnable().minHeightProperty(), currentFieldHeight, Interpolator.EASE_BOTH)),
+ new KeyFrame(Duration.millis(160),
+ // text pane animation
+ new KeyValue(textPane.translateYProperty(), (initYLayout + textPane.getMaxHeight()/2) - newHeight/2, Interpolator.EASE_BOTH),
+ // animate the height change effect
+ new KeyValue(getSkinnable().minHeightProperty(), newHeight, Interpolator.EASE_BOTH)));
+ errorAnimation.play();
+ // show the error label when finished
+ errorAnimation.setOnFinished(finish->new Timeline(new KeyFrame(Duration.millis(160),new KeyValue(errorContainer.opacityProperty(), 1, Interpolator.EASE_BOTH))).play());
+ currentFieldHeight = newHeight;
+ oldErrorLabelHeight = newVal.doubleValue();
+ }
+ });
+ errorContainer.visibleProperty().addListener((o,oldVal,newVal)->{
+ // show the error label if it's not shown
+ if(newVal) new Timeline(new KeyFrame(Duration.millis(160),new KeyValue(errorContainer.opacityProperty(), 1, Interpolator.EASE_BOTH))).play();
+ });
+
+
+ field.labelFloatProperty().addListener((o,oldVal,newVal)->{
+ if(newVal) JFXUtilities.runInFX(()->createFloatingLabel());
+ else promptText.visibleProperty().bind(usePromptText);
+ createFocusTransition();
+ });
+
+ field.activeValidatorProperty().addListener((o,oldVal,newVal)->{
+ if(textPane != null){
+ if(!((JFXTextField)getSkinnable()).isDisableAnimation()){
+ if(hideErrorAnimation!=null && hideErrorAnimation.getStatus().equals(Status.RUNNING))
+ hideErrorAnimation.stop();
+ if(newVal!=null){
+ hideErrorAnimation = new Timeline(new KeyFrame(Duration.millis(160),new KeyValue(errorContainer.opacityProperty(), 0, Interpolator.EASE_BOTH)));
+ hideErrorAnimation.setOnFinished(finish->{
+ errorContainer.setVisible(false);
+ JFXUtilities.runInFX(()->showError(newVal));
+ });
+ hideErrorAnimation.play();
+ }else{
+ JFXUtilities.runInFX(()->hideError());
+ }
+ }else{
+ if(newVal!=null) JFXUtilities.runInFXAndWait(()->showError(newVal));
+ else JFXUtilities.runInFXAndWait(()->hideError());
+ }
+ }
+ });
+
+ field.focusColorProperty().addListener((o,oldVal,newVal)->{
+ if(newVal!=null) {
+ focusedLine.setBackground(new Background(new BackgroundFill(newVal, CornerRadii.EMPTY, Insets.EMPTY)));
+ if(((JFXTextField)getSkinnable()).isLabelFloat()){
+ promptTextColorTransition = new CachedTransition(textPane, new Timeline(
+ new KeyFrame(Duration.millis(1300),
+ new KeyValue(promptTextFill, newVal, Interpolator.EASE_BOTH))))
+ {
+ {setDelay(Duration.millis(0)); setCycleDuration(Duration.millis(160));}
+ protected void starting() {super.starting(); oldPromptTextFill = promptTextFill.get();}
+ };
+ // reset transition
+ transition = null;
+ }
+ }
+ });
+ field.unFocusColorProperty().addListener((o,oldVal,newVal)->{
+ if(newVal!=null)
+ line.setBackground(new Background(new BackgroundFill(newVal, CornerRadii.EMPTY, Insets.EMPTY)));
+ });
+
+ // handle animation on focus gained/lost event
+ field.focusedProperty().addListener((o,oldVal,newVal) -> {
+ if (newVal) focus();
+ else unFocus();
+ });
+
+ // handle text changing at runtime
+ field.textProperty().addListener((o,oldVal,newVal)->{
+ if(!getSkinnable().isFocused() && ((JFXTextField)getSkinnable()).isLabelFloat()){
+ if(newVal == null || newVal.isEmpty()) animateFloatingLabel(false);
+ else animateFloatingLabel(true);
+ }
+ });
+
+ field.disabledProperty().addListener((o,oldVal,newVal) -> {
+ line.setBorder(newVal ? new Border(new BorderStroke(((JFXTextField)getSkinnable()).getUnFocusColor(),
+ BorderStrokeStyle.DASHED, CornerRadii.EMPTY, new BorderWidths(line.getHeight()))) : Border.EMPTY);
+ line.setBackground(new Background(new BackgroundFill( newVal? Color.TRANSPARENT : ((JFXTextField)getSkinnable()).getUnFocusColor(),
+ CornerRadii.EMPTY, Insets.EMPTY)));
+ });
+
+ // prevent setting prompt text fill to transparent when text field is focused (override java transparent color if the control was focused)
+ promptTextFill.addListener((o,oldVal,newVal)->{
+ if(Color.TRANSPARENT.equals(newVal) && ((JFXTextField)getSkinnable()).isLabelFloat())
+ promptTextFill.set(oldVal);
+ });
+
+ }
+
+ @Override
+ protected void layoutChildren(final double x, final double y, final double w, final double h) {
+ super.layoutChildren(x, y, w, h);
+
+ // change control properties if and only if animations are stopped
+ if((transition == null || transition.getStatus().equals(Status.STOPPED))){
+ if(getSkinnable().isFocused() && ((JFXTextField)getSkinnable()).isLabelFloat()){
+ promptTextFill.set(((JFXTextField)getSkinnable()).getFocusColor());
+ }
+ }
+
+ if(invalid){
+ invalid = false;
+ textPane = ((Pane)this.getChildren().get(0));
+ // create floating label
+ createFloatingLabel();
+ // to position the prompt node properly
+ super.layoutChildren(x, y, w, h);
+ // update validation container
+ if(((JFXTextField)getSkinnable()).getActiveValidator()!=null) updateValidationError();
+ // focus
+ createFocusTransition();
+ if(getSkinnable().isFocused()) focus();
+ }
+
+ focusedLine.resizeRelocate(x, getSkinnable().getHeight(), w, focusedLine.prefHeight(-1));
+ line.resizeRelocate(x, getSkinnable().getHeight(), w, line.prefHeight(-1));
+ errorContainer.relocate(x, getSkinnable().getHeight() + focusedLine.getHeight());
+ scale.setPivotX(w/2);
+ }
+
+ private void updateValidationError() {
+ if(hideErrorAnimation!=null && hideErrorAnimation.getStatus().equals(Status.RUNNING))
+ hideErrorAnimation.stop();
+ hideErrorAnimation = new Timeline(
+ new KeyFrame(Duration.millis(160),
+ new KeyValue(errorContainer.opacityProperty(), 0, Interpolator.EASE_BOTH)));
+ hideErrorAnimation.setOnFinished(finish->{
+ errorContainer.setVisible(false);
+ showError(((JFXTextField)getSkinnable()).getActiveValidator());
+ });
+ hideErrorAnimation.play();
+ }
+
+
+ private void createFloatingLabel() {
+ if(((JFXTextField)getSkinnable()).isLabelFloat()){
+ if(promptText == null){
+ // get the prompt text node or create it
+ boolean triggerFloatLabel = false;
+ if(textPane.getChildren().get(0) instanceof Text) promptText = (Text) textPane.getChildren().get(0);
+ else{
+ Field field;
+ try {
+ field = TextFieldSkin.class.getDeclaredField("promptNode");
+ field.setAccessible(true);
+ createPromptNode();
+ field.set(this, promptText);
+ // position the prompt node in its position
+ triggerFloatLabel = true;
+ } catch (NoSuchFieldException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (SecurityException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IllegalArgumentException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ promptText.getTransforms().add(promptTextScale);
+ promptContainer.getChildren().add(promptText);
+
+ if(triggerFloatLabel){
+ promptText.setTranslateY(-textPane.getHeight());
+ promptTextScale.setX(0.85);
+ promptTextScale.setY(0.85);
+ }
+ }
+
+ promptTextUpTransition = new CachedTransition(textPane, new Timeline(
+ new KeyFrame(Duration.millis(1300),
+ new KeyValue(promptText.translateYProperty(), -textPane.getHeight(), Interpolator.EASE_BOTH),
+ new KeyValue(promptTextScale.xProperty(), 0.85 , Interpolator.EASE_BOTH),
+ new KeyValue(promptTextScale.yProperty(), 0.85 , Interpolator.EASE_BOTH)))){{ setDelay(Duration.millis(0)); setCycleDuration(Duration.millis(240)); }};
+
+ promptTextColorTransition = new CachedTransition(textPane, new Timeline(
+ new KeyFrame(Duration.millis(1300),
+ new KeyValue(promptTextFill, ((JFXTextField)getSkinnable()).getFocusColor(), Interpolator.EASE_BOTH))))
+ {
+ { setDelay(Duration.millis(0)); setCycleDuration(Duration.millis(160)); }
+ protected void starting() {super.starting(); oldPromptTextFill = promptTextFill.get();};
+ };
+
+ promptTextDownTransition = new CachedTransition(textPane, new Timeline(
+ new KeyFrame(Duration.millis(1300),
+ new KeyValue(promptText.translateYProperty(), 0, Interpolator.EASE_BOTH),
+ new KeyValue(promptTextScale.xProperty(), 1 , Interpolator.EASE_BOTH),
+ new KeyValue(promptTextScale.yProperty(), 1 , Interpolator.EASE_BOTH))))
+ {{ setDelay(Duration.millis(0)); setCycleDuration(Duration.millis(240));}};
+ promptTextDownTransition.setOnFinished((finish)->{
+ promptText.setTranslateY(0);
+ promptTextScale.setX(1);
+ promptTextScale.setY(1);
+ });
+ promptText.visibleProperty().unbind();
+ promptText.visibleProperty().set(true);
+ }
+ }
+
+ private void createPromptNode(){
+ promptText = new Text();
+ promptText.setManaged(false);
+ promptText.getStyleClass().add("text");
+ promptText.visibleProperty().bind(usePromptText);
+ promptText.fontProperty().bind(getSkinnable().fontProperty());
+ promptText.textProperty().bind(getSkinnable().promptTextProperty());
+ promptText.fillProperty().bind(promptTextFill);
+ promptText.setLayoutX(1);
+ }
+
+ private void focus(){
+ /*
+ * in case the method request layout is not called before focused
+ * this is bug is reported while editing TreeTableView cells
+ */
+ if(textPane == null){
+ Platform.runLater(()->focus());
+ }else{
+ // create the focus animations
+ if(transition == null) createFocusTransition();
+ transition.play();
+ }
+ }
+
+ private void createFocusTransition() {
+ transition = new ParallelTransition();
+ if(((JFXTextField)getSkinnable()).isLabelFloat()){
+ transition.getChildren().add(promptTextUpTransition);
+ transition.getChildren().add(promptTextColorTransition);
+ }
+ transition.getChildren().add(linesAnimation);
+ }
+
+ private void unFocus() {
+ if(transition!=null) transition.stop();
+ scale.setX(initScale);
+ focusedLine.setOpacity(0);
+ if(((JFXTextField)getSkinnable()).isLabelFloat() && oldPromptTextFill != null){
+ promptTextFill.set(oldPromptTextFill);
+ if(usePromptText()) promptTextDownTransition.play();
+ }
+ }
+
+ /**
+ * this method is called when the text property is changed when the
+ * field is not focused (changed in code)
+ * @param up
+ */
+ private void animateFloatingLabel(boolean up){
+ if(promptText == null){
+ Platform.runLater(()-> animateFloatingLabel(up));
+ }else{
+ if(transition!=null){
+ transition.stop();
+ transition.getChildren().remove(promptTextUpTransition);
+ transition = null;
+ }
+ if(up && promptText.getTranslateY() == 0){
+ promptTextDownTransition.stop();
+ promptTextUpTransition.play();
+ }else if(!up){
+ promptTextUpTransition.stop();
+ promptTextDownTransition.play();
+ }
+ }
+ }
+
+ private boolean usePromptText() {
+ String txt = getSkinnable().getText();
+ String promptTxt = getSkinnable().getPromptText();
+ boolean hasPromptText = (txt == null || txt.isEmpty()) && promptTxt != null && !promptTxt.isEmpty() && !promptTextFill.get().equals(Color.TRANSPARENT);
+ return hasPromptText;
+ }
+
+ private void showError(ValidatorBase validator){
+ // set text in error label
+ errorLabel.setText(validator.getMessage());
+ // show error icon
+ Node awsomeIcon = validator.getIcon();
+ errorIcon.getChildren().clear();
+ if(awsomeIcon!=null){
+ errorIcon.getChildren().add(awsomeIcon);
+ StackPane.setAlignment(awsomeIcon, Pos.TOP_RIGHT);
+ }
+ // init only once, to fix the text pane from resizing
+ if(initYLayout == -1){
+ textPane.setMaxHeight(textPane.getHeight());
+ initYLayout = textPane.getBoundsInParent().getMinY();
+ initHeight = getSkinnable().getHeight();
+ currentFieldHeight = initHeight;
+ }
+ errorContainer.setVisible(true);
+ errorShown = true;
+ }
+
+ private void hideError(){
+ if(heightChanged){
+ new Timeline(new KeyFrame(Duration.millis(160), new KeyValue(textPane.translateYProperty(), 0, Interpolator.EASE_BOTH))).play();
+ // reset the height of text field
+ new Timeline(new KeyFrame(Duration.millis(160), new KeyValue(getSkinnable().minHeightProperty(), initHeight, Interpolator.EASE_BOTH))).play();
+ heightChanged = false;
+ }
+ // clear error label text
+ errorLabel.setText(null);
+ oldErrorLabelHeight = errorLabelInitHeight;
+ // clear error icon
+ errorIcon.getChildren().clear();
+ // reset the height of the text field
+ currentFieldHeight = initHeight;
+ // hide error container
+ errorContainer.setVisible(false);
+ errorShown = false;
+ }
+}
diff --git a/src/com/jfoenix/animation/JFXNodesAnimation.java b/jfoenix/src/main/java/com/jfoenix/animation/JFXNodesAnimation.java
similarity index 96%
rename from src/com/jfoenix/animation/JFXNodesAnimation.java
rename to jfoenix/src/main/java/com/jfoenix/animation/JFXNodesAnimation.java
index 6d5622bd..153c4681 100644
--- a/src/com/jfoenix/animation/JFXNodesAnimation.java
+++ b/jfoenix/src/main/java/com/jfoenix/animation/JFXNodesAnimation.java
@@ -1,55 +1,55 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package com.jfoenix.animation;
-
-import javafx.animation.Animation;
-import javafx.scene.Node;
-
-public abstract class JFXNodesAnimation {
-
- protected S fromNode;
- protected T toNode;
-
- public JFXNodesAnimation(S fromNode, T toNode) {
- this.fromNode = fromNode;
- this.toNode = toNode;
- }
-
- public void animate(){
- init();
- Animation exitAnimation = animateExit();
- Animation sharedAnimation = animateSharedNodes();
- Animation enteranceAnimation = animateEntrance();
- exitAnimation.setOnFinished((finish)-> sharedAnimation.play());
- sharedAnimation.setOnFinished((finish)->enteranceAnimation.play());
- enteranceAnimation.setOnFinished((finish)->end());
- exitAnimation.play();
- }
-
- public abstract Animation animateExit();
-
- public abstract Animation animateSharedNodes();
-
- public abstract Animation animateEntrance();
-
- public abstract void init();
-
- public abstract void end();
-
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package com.jfoenix.animation;
+
+import javafx.animation.Animation;
+import javafx.scene.Node;
+
+public abstract class JFXNodesAnimation {
+
+ protected S fromNode;
+ protected T toNode;
+
+ public JFXNodesAnimation(S fromNode, T toNode) {
+ this.fromNode = fromNode;
+ this.toNode = toNode;
+ }
+
+ public void animate(){
+ init();
+ Animation exitAnimation = animateExit();
+ Animation sharedAnimation = animateSharedNodes();
+ Animation enteranceAnimation = animateEntrance();
+ exitAnimation.setOnFinished((finish)-> sharedAnimation.play());
+ sharedAnimation.setOnFinished((finish)->enteranceAnimation.play());
+ enteranceAnimation.setOnFinished((finish)->end());
+ exitAnimation.play();
+ }
+
+ public abstract Animation animateExit();
+
+ public abstract Animation animateSharedNodes();
+
+ public abstract Animation animateEntrance();
+
+ public abstract void init();
+
+ public abstract void end();
+
+}
diff --git a/src/com/jfoenix/concurrency/JFXUtilities.java b/jfoenix/src/main/java/com/jfoenix/concurrency/JFXUtilities.java
similarity index 96%
rename from src/com/jfoenix/concurrency/JFXUtilities.java
rename to jfoenix/src/main/java/com/jfoenix/concurrency/JFXUtilities.java
index 0f49e587..50491719 100644
--- a/src/com/jfoenix/concurrency/JFXUtilities.java
+++ b/jfoenix/src/main/java/com/jfoenix/concurrency/JFXUtilities.java
@@ -1,80 +1,80 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package com.jfoenix.concurrency;
-
-import javafx.application.Platform;
-
-import java.util.concurrent.CountDownLatch;
-
-
-/**
- * JavaFX FX Thread utilities
- * JFXUtilities allow sync mechanism to the FX thread
- *
- * @author pmoufarrej - * @version 1.0 - * @since 2016-03-09 - */ - -public class JFXUtilities { - - /** - * This method is used to run a specified Runnable in the FX Application thread, - * it returns before the task finished execution - * - * @param doRun This is the sepcifed task to be excuted by the FX Application thread - * @return Nothing - */ - public static void runInFX(Runnable doRun) { - if (Platform.isFxApplicationThread()) { - doRun.run(); - return; - } - Platform.runLater(doRun); - } - - /** - * This method is used to run a specified Runnable in the FX Application thread, - * it waits for the task to finish before returning to the main thread. - * - * @param doRun This is the sepcifed task to be excuted by the FX Application thread - * @return Nothing - */ - public static void runInFXAndWait(Runnable doRun) { - if (Platform.isFxApplicationThread()) { - doRun.run(); - return; - } - final CountDownLatch doneLatch = new CountDownLatch(1); - Platform.runLater(() -> { - try { - doRun.run(); - } - finally { - doneLatch.countDown(); - } - }); - try { - doneLatch.await(); - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.jfoenix.concurrency; + +import javafx.application.Platform; + +import java.util.concurrent.CountDownLatch; + + +/** + *
+ * @author pmoufarrej
+ * @version 1.0
+ * @since 2016-03-09
+ */
+
+public class JFXUtilities {
+
+ /**
+ * This method is used to run a specified Runnable in the FX Application thread,
+ * it returns before the task finished execution
+ *
+ * @param doRun This is the sepcifed task to be excuted by the FX Application thread
+ * @return Nothing
+ */
+ public static void runInFX(Runnable doRun) {
+ if (Platform.isFxApplicationThread()) {
+ doRun.run();
+ return;
+ }
+ Platform.runLater(doRun);
+ }
+
+ /**
+ * This method is used to run a specified Runnable in the FX Application thread,
+ * it waits for the task to finish before returning to the main thread.
+ *
+ * @param doRun This is the sepcifed task to be excuted by the FX Application thread
+ * @return Nothing
+ */
+ public static void runInFXAndWait(Runnable doRun) {
+ if (Platform.isFxApplicationThread()) {
+ doRun.run();
+ return;
+ }
+ final CountDownLatch doneLatch = new CountDownLatch(1);
+ Platform.runLater(() -> {
+ try {
+ doRun.run();
+ }
+ finally {
+ doneLatch.countDown();
+ }
+ });
+ try {
+ doneLatch.await();
+ }
+ catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+}
diff --git a/src/com/jfoenix/controls/JFXBadge.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXBadge.java
similarity index 96%
rename from src/com/jfoenix/controls/JFXBadge.java
rename to jfoenix/src/main/java/com/jfoenix/controls/JFXBadge.java
index 6156eced..4fb2eb28 100644
--- a/src/com/jfoenix/controls/JFXBadge.java
+++ b/jfoenix/src/main/java/com/jfoenix/controls/JFXBadge.java
@@ -1,249 +1,249 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package com.jfoenix.controls;
-
-import javafx.animation.FadeTransition;
-import javafx.beans.DefaultProperty;
-import javafx.beans.property.ObjectProperty;
-import javafx.beans.property.SimpleObjectProperty;
-import javafx.beans.property.SimpleStringProperty;
-import javafx.beans.property.StringProperty;
-import javafx.geometry.Pos;
-import javafx.scene.Group;
-import javafx.scene.Node;
-import javafx.scene.control.Label;
-import javafx.scene.layout.Region;
-import javafx.scene.layout.StackPane;
-import javafx.util.Duration;
-
-@DefaultProperty(value = "control")
-public class JFXBadge extends StackPane {
-
- private Group badge;
- protected Node control;
- private boolean enabled = true;
-
- public JFXBadge() {
- this(null);
- }
-
- public JFXBadge(Node control) {
- this(control, Pos.TOP_RIGHT);
- }
-
- public JFXBadge(Node control, Pos pos) {
- super();
- initialize();
- setPosition(pos);
- setControl(control);
- position.addListener((o,oldVal,newVal)-> StackPane.setAlignment(badge, newVal));
- }
-
- /***************************************************************************
- * * Setters / Getters * *
- **************************************************************************/
-
- public void setControl(Node control) {
- if (control != null) {
- this.control = control;
- this.badge = new Group();
- this.getChildren().add(control);
- this.getChildren().add(badge);
-
- // if the control got resized the badge must be rest
- if (control instanceof Region) {
- ((Region) control).widthProperty().addListener((o, oldVal, newVal) -> refreshBadge());
- ((Region) control).heightProperty().addListener((o, oldVal, newVal) -> refreshBadge());
- }
- text.addListener((o, oldVal, newVal) -> refreshBadge());
- }
- }
-
- public Node getControl() {
- return this.control;
- }
-
- public void setEnabled(boolean enable) {
- this.enabled = enable;
- }
-
- public void refreshBadge() {
- badge.getChildren().clear();
- if (enabled) {
-
-// final double scaledWidth = control.getLayoutBounds().getWidth() / getBadgeScale().doubleValue();
-// final double scaledHeight = control.getLayoutBounds().getHeight() / getBadgeScale().doubleValue();
-
-// Shape background = new Rectangle(scaledWidth, scaledHeight);
-// Shape clip = new Rectangle(scaledWidth, scaledHeight);
-//
-// if (maskType.get().equals(JFXBadge.BadgeMask.CIRCLE)) {
-// double radius = Math.min(scaledWidth / 2, scaledHeight / 2);
-// background = new Circle(radius);
-// clip = new Circle(radius);
-// }
-//
-//
-// if (badgeFill.get() instanceof Color) {
-// Color circleColor = new Color(((Color) badgeFill.get()).getRed(), ((Color) badgeFill.get()).getGreen(),
-// ((Color) badgeFill.get()).getBlue(), ((Color) badgeFill.get()).getOpacity());
-// background.setStroke(circleColor);
-// background.setFill(circleColor);
-// } else {
-// background.setStroke(badgeFill.get());
-// background.setFill(badgeFill.get());
-// }
-
- Label labelControl = new Label(text.getValue());
-
- StackPane badgePane = new StackPane();
-// badgePane.getChildren().add(background);
- badgePane.getStyleClass().add("badge-pane");
- badgePane.getChildren().add(labelControl);
- //Adding a clip would avoid overlap but this does not work as intended
- //badgePane.setClip(clip);
- badge.getChildren().add(badgePane);
- StackPane.setAlignment(badge, getPosition());
-
- FadeTransition ft = new FadeTransition(Duration.millis(666), badge);
- ft.setFromValue(0);
- ft.setToValue(1.0);
- ft.setCycleCount(1);
- ft.setAutoReverse(true);
- ft.play();
-
- }
- }
-
- /***************************************************************************
- * * Stylesheet Handling * *
- **************************************************************************/
-
- private static final String DEFAULT_STYLE_CLASS = "jfx-badge";
-
- private void initialize() {
- this.getStyleClass().add(DEFAULT_STYLE_CLASS);
- }
-
-// private StyleableObjectProperty
- *
- *
- * @param dialogContainer is the parent of the dialog, it
- * @param content the content of dialog
- * @param transitionType the animation type
- */
-
- public JFXDialog(StackPane dialogContainer, Region content, DialogTransition transitionType) {
- initialize();
- setContent(content);
- setDialogContainer(dialogContainer);
- this.transitionType.set(transitionType);
- // init change listeners
- initChangeListeners();
- }
-
- /**
- * creates JFXDialog control with a specified animation type that
- * is closed when clicking on the overlay, the animation type
- * can be one of the following:
- *
- *
- *
- * @param dialogContainer
- * @param content
- * @param transitionType
- * @param overlayClose
- */
- public JFXDialog(StackPane dialogContainer, Region content, DialogTransition transitionType, boolean overlayClose) {
- initialize();
- setOverlayClose(overlayClose);
- setContent(content);
- setDialogContainer(dialogContainer);
- this.transitionType.set(transitionType);
- // init change listeners
- initChangeListeners();
- }
-
- private void initChangeListeners(){
- overlayCloseProperty().addListener((o,oldVal,newVal)->{
- if(newVal) this.addEventHandler(MouseEvent.MOUSE_PRESSED, closeHandler);
- else this.removeEventHandler(MouseEvent.MOUSE_PRESSED, closeHandler);
- });
- }
-
- private void initialize() {
- this.setVisible(false);
- this.getStyleClass().add(DEFAULT_STYLE_CLASS);
- this.transitionType.addListener((o,oldVal,newVal)->{
- animation = getShowAnimation(transitionType.get());
- });
-
- contentHolder = new StackPane();
- contentHolder.setBackground(new Background(new BackgroundFill(Color.WHITE, new CornerRadii(2), null)));
- JFXDepthManager.setDepth(contentHolder, 4);
- contentHolder.setPickOnBounds(false);
- // ensure stackpane is never resized beyond it's preferred size
- contentHolder.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
- this.getChildren().add(contentHolder);
- this.getStyleClass().add("jfx-dialog-overlay-pane");
- StackPane.setAlignment(contentHolder, Pos.CENTER);
- this.setBackground(new Background(new BackgroundFill(Color.rgb(0, 0, 0, 0.1), null, null)));
- // close the dialog if clicked on the overlay pane
- if(overlayClose.get()) this.addEventHandler(MouseEvent.MOUSE_PRESSED, closeHandler);
- // prevent propagating the events to overlay pane
- contentHolder.addEventHandler(MouseEvent.ANY, (e)->e.consume());
- }
-
- /***************************************************************************
- * *
- * Setters / Getters *
- * *
- **************************************************************************/
-
- /**
- * @return the dialog container
- */
- public StackPane getDialogContainer() {
- return dialogContainer;
- }
-
- /**
- * set the dialog container
- * Note: the dialog container must be StackPane, its the container for the dialog to be shown in.
- *
- * @param dialogContainer
- */
- public void setDialogContainer(StackPane dialogContainer) {
- if(dialogContainer!=null){
- this.dialogContainer = dialogContainer;
- if(this.dialogContainer.getChildren().indexOf(this)==-1 || this.dialogContainer.getChildren().indexOf(this)!=this.dialogContainer.getChildren().size()-1){
- this.dialogContainer.getChildren().remove(this);
- this.dialogContainer.getChildren().add(this);
- }
- // FIXME: need to be improved to consider only the parent boundary
- offsetX = (this.getParent().getBoundsInLocal().getWidth());
- offsetY = (this.getParent().getBoundsInLocal().getHeight());
- animation = getShowAnimation(transitionType.get());
- }
- }
-
- /**
- * @return dialog content node
- */
- public Region getContent() {
- return content;
- }
-
- /**
- * set the content of the dialog
- * @param content
- */
- public void setContent(Region content) {
- if(content!=null){
- this.content = content;
- this.content.setPickOnBounds(false);
- contentHolder.getChildren().add(content);
- }
- }
-
- /**
- * indicates whether the dialog will close when clicking on the overlay or not
- * @return
- */
- private BooleanProperty overlayClose = new SimpleBooleanProperty(true);
-
- public final BooleanProperty overlayCloseProperty() {
- return this.overlayClose;
- }
- public final boolean isOverlayClose() {
- return this.overlayCloseProperty().get();
- }
- public final void setOverlayClose(final boolean overlayClose) {
- this.overlayCloseProperty().set(overlayClose);
- }
-
- /**
- * it will show the dialog in the specified container
- * @param dialogContainer
- */
- public void show(StackPane dialogContainer){
- this.setDialogContainer(dialogContainer);
- animation.play();
- }
-
- /**
- * show the dialog inside its parent container
- */
- public void show(){
- this.setDialogContainer(dialogContainer);
- // animation = getShowAnimation(transitionType.get());
- animation.play();
- }
-
- /**
- * close the dialog
- */
- public void close(){
- animation.setRate(-1);
- animation.play();
- animation.setOnFinished((e)->{
- resetProperties();
- onDialogClosedProperty.get().handle(new JFXDialogEvent(JFXDialogEvent.CLOSED));
- dialogContainer.getChildren().remove(this);
- });
- }
-
- /***************************************************************************
- * *
- * Transitions *
- * *
- **************************************************************************/
-
- private Transition getShowAnimation(DialogTransition transitionType){
- Transition animation = null;
- if(contentHolder!=null){
- switch (transitionType) {
- case LEFT:
- contentHolder.setScaleX(1);
- contentHolder.setScaleY(1);
- contentHolder.setTranslateX(-offsetX);
- animation = new LeftTransition();
- break;
- case RIGHT:
- contentHolder.setScaleX(1);
- contentHolder.setScaleY(1);
- contentHolder.setTranslateX(offsetX);
- animation = new RightTransition();
- break;
- case TOP:
- contentHolder.setScaleX(1);
- contentHolder.setScaleY(1);
- contentHolder.setTranslateY(-offsetY);
- animation = new TopTransition();
- break;
- case BOTTOM:
- contentHolder.setScaleX(1);
- contentHolder.setScaleY(1);
- contentHolder.setTranslateY(offsetY);
- animation = new BottomTransition();
- break;
- default:
- contentHolder.setScaleX(0);
- contentHolder.setScaleY(0);
- animation = new CenterTransition();
- break;
- }
- }
- if(animation!=null)animation.setOnFinished((finish)->onDialogOpenedProperty.get().handle(new JFXDialogEvent(JFXDialogEvent.OPENED)));
- return animation;
- }
-
- private void resetProperties(){
- this.setVisible(false);
- contentHolder.setTranslateX(0);
- contentHolder.setTranslateY(0);
- contentHolder.setScaleX(1);
- contentHolder.setScaleY(1);
- }
-
- private class LeftTransition extends CachedTransition {
- public LeftTransition() {
- super(contentHolder, new Timeline(
- new KeyFrame(Duration.ZERO,
- new KeyValue(contentHolder.translateXProperty(), -offsetX ,Interpolator.EASE_BOTH),
- new KeyValue(JFXDialog.this.visibleProperty(), false ,Interpolator.EASE_BOTH)
- ),
- new KeyFrame(Duration.millis(10),
- new KeyValue(JFXDialog.this.visibleProperty(), true ,Interpolator.EASE_BOTH),
- new KeyValue(JFXDialog.this.opacityProperty(), 0,Interpolator.EASE_BOTH)
- ),
- new KeyFrame(Duration.millis(1000),
- new KeyValue(contentHolder.translateXProperty(), 0,Interpolator.EASE_BOTH),
- new KeyValue(JFXDialog.this.opacityProperty(), 1,Interpolator.EASE_BOTH)
- ))
- );
- // reduce the number to increase the shifting , increase number to reduce shifting
- setCycleDuration(Duration.seconds(0.4));
- setDelay(Duration.seconds(0));
- }
- }
-
- private class RightTransition extends CachedTransition {
- public RightTransition() {
- super(contentHolder, new Timeline(
- new KeyFrame(Duration.ZERO,
- new KeyValue(contentHolder.translateXProperty(), offsetX ,Interpolator.EASE_BOTH),
- new KeyValue(JFXDialog.this.visibleProperty(), false ,Interpolator.EASE_BOTH)
- ),
- new KeyFrame(Duration.millis(10),
- new KeyValue(JFXDialog.this.visibleProperty(), true ,Interpolator.EASE_BOTH),
- new KeyValue(JFXDialog.this.opacityProperty(), 0, Interpolator.EASE_BOTH)
- ),
- new KeyFrame(Duration.millis(1000),
- new KeyValue(contentHolder.translateXProperty(), 0,Interpolator.EASE_BOTH),
- new KeyValue(JFXDialog.this.opacityProperty(), 1, Interpolator.EASE_BOTH)))
- );
- // reduce the number to increase the shifting , increase number to reduce shifting
- setCycleDuration(Duration.seconds(0.4));
- setDelay(Duration.seconds(0));
- }
- }
-
- private class TopTransition extends CachedTransition {
- public TopTransition() {
- super(contentHolder, new Timeline(
- new KeyFrame(Duration.ZERO,
- new KeyValue(contentHolder.translateYProperty(), -offsetY ,Interpolator.EASE_BOTH),
- new KeyValue(JFXDialog.this.visibleProperty(), false ,Interpolator.EASE_BOTH)
- ),
- new KeyFrame(Duration.millis(10),
- new KeyValue(JFXDialog.this.visibleProperty(), true ,Interpolator.EASE_BOTH),
- new KeyValue(JFXDialog.this.opacityProperty(), 0, Interpolator.EASE_BOTH)
- ),
- new KeyFrame(Duration.millis(1000),
- new KeyValue(contentHolder.translateYProperty(), 0,Interpolator.EASE_BOTH),
- new KeyValue(JFXDialog.this.opacityProperty(), 1, Interpolator.EASE_BOTH)))
- );
- // reduce the number to increase the shifting , increase number to reduce shifting
- setCycleDuration(Duration.seconds(0.4));
- setDelay(Duration.seconds(0));
- }
- }
-
- private class BottomTransition extends CachedTransition {
- public BottomTransition() {
- super(contentHolder, new Timeline(
- new KeyFrame(Duration.ZERO,
- new KeyValue(contentHolder.translateYProperty(), offsetY ,Interpolator.EASE_BOTH),
- new KeyValue(JFXDialog.this.visibleProperty(), false ,Interpolator.EASE_BOTH)
- ),
- new KeyFrame(Duration.millis(10),
- new KeyValue(JFXDialog.this.visibleProperty(), true ,Interpolator.EASE_BOTH),
- new KeyValue(JFXDialog.this.opacityProperty(), 0, Interpolator.EASE_BOTH)
- ),
- new KeyFrame(Duration.millis(1000),
- new KeyValue(contentHolder.translateYProperty(), 0,Interpolator.EASE_BOTH),
- new KeyValue(JFXDialog.this.opacityProperty(), 1, Interpolator.EASE_BOTH)))
- );
- // reduce the number to increase the shifting , increase number to reduce shifting
- setCycleDuration(Duration.seconds(0.4));
- setDelay(Duration.seconds(0));
- }
- }
-
- private class CenterTransition extends CachedTransition {
- public CenterTransition() {
- super(contentHolder, new Timeline(
- new KeyFrame(Duration.ZERO,
- new KeyValue(contentHolder.scaleXProperty(), 0 ,Interpolator.EASE_BOTH),
- new KeyValue(contentHolder.scaleYProperty(), 0 ,Interpolator.EASE_BOTH),
- new KeyValue(JFXDialog.this.visibleProperty(), false ,Interpolator.EASE_BOTH)
- ),
- new KeyFrame(Duration.millis(10),
- new KeyValue(JFXDialog.this.visibleProperty(), true ,Interpolator.EASE_BOTH),
- new KeyValue(JFXDialog.this.opacityProperty(), 0,Interpolator.EASE_BOTH)
- ),
- new KeyFrame(Duration.millis(1000),
- new KeyValue(contentHolder.scaleXProperty(), 1 ,Interpolator.EASE_BOTH),
- new KeyValue(contentHolder.scaleYProperty(), 1 ,Interpolator.EASE_BOTH),
- new KeyValue(JFXDialog.this.opacityProperty(), 1, Interpolator.EASE_BOTH)
- ))
- );
- // reduce the number to increase the shifting , increase number to reduce shifting
- setCycleDuration(Duration.seconds(0.4));
- setDelay(Duration.seconds(0));
- }
- }
-
-
- /***************************************************************************
- * *
- * Stylesheet Handling *
- * *
- **************************************************************************/
- /**
- * Initialize the style class to 'jfx-dialog'.
- *
- * This is the selector class from which CSS can be used to style
- * this control.
- */
- private static final String DEFAULT_STYLE_CLASS = "jfx-dialog";
-
- /**
- * dialog transition type property, it can be one of the following:
- *
- *
- */
- private StyleableObjectProperty
+ *
+ *
+ * @param dialogContainer is the parent of the dialog, it
+ * @param content the content of dialog
+ * @param transitionType the animation type
+ */
+
+ public JFXDialog(StackPane dialogContainer, Region content, DialogTransition transitionType) {
+ initialize();
+ setContent(content);
+ setDialogContainer(dialogContainer);
+ this.transitionType.set(transitionType);
+ // init change listeners
+ initChangeListeners();
+ }
+
+ /**
+ * creates JFXDialog control with a specified animation type that
+ * is closed when clicking on the overlay, the animation type
+ * can be one of the following:
+ *
+ *
+ *
+ * @param dialogContainer
+ * @param content
+ * @param transitionType
+ * @param overlayClose
+ */
+ public JFXDialog(StackPane dialogContainer, Region content, DialogTransition transitionType, boolean overlayClose) {
+ initialize();
+ setOverlayClose(overlayClose);
+ setContent(content);
+ setDialogContainer(dialogContainer);
+ this.transitionType.set(transitionType);
+ // init change listeners
+ initChangeListeners();
+ }
+
+ private void initChangeListeners(){
+ overlayCloseProperty().addListener((o,oldVal,newVal)->{
+ if(newVal) this.addEventHandler(MouseEvent.MOUSE_PRESSED, closeHandler);
+ else this.removeEventHandler(MouseEvent.MOUSE_PRESSED, closeHandler);
+ });
+ }
+
+ private void initialize() {
+ this.setVisible(false);
+ this.getStyleClass().add(DEFAULT_STYLE_CLASS);
+ this.transitionType.addListener((o,oldVal,newVal)->{
+ animation = getShowAnimation(transitionType.get());
+ });
+
+ contentHolder = new StackPane();
+ contentHolder.setBackground(new Background(new BackgroundFill(Color.WHITE, new CornerRadii(2), null)));
+ JFXDepthManager.setDepth(contentHolder, 4);
+ contentHolder.setPickOnBounds(false);
+ // ensure stackpane is never resized beyond it's preferred size
+ contentHolder.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
+ this.getChildren().add(contentHolder);
+ this.getStyleClass().add("jfx-dialog-overlay-pane");
+ StackPane.setAlignment(contentHolder, Pos.CENTER);
+ this.setBackground(new Background(new BackgroundFill(Color.rgb(0, 0, 0, 0.1), null, null)));
+ // close the dialog if clicked on the overlay pane
+ if(overlayClose.get()) this.addEventHandler(MouseEvent.MOUSE_PRESSED, closeHandler);
+ // prevent propagating the events to overlay pane
+ contentHolder.addEventHandler(MouseEvent.ANY, (e)->e.consume());
+ }
+
+ /***************************************************************************
+ * *
+ * Setters / Getters *
+ * *
+ **************************************************************************/
+
+ /**
+ * @return the dialog container
+ */
+ public StackPane getDialogContainer() {
+ return dialogContainer;
+ }
+
+ /**
+ * set the dialog container
+ * Note: the dialog container must be StackPane, its the container for the dialog to be shown in.
+ *
+ * @param dialogContainer
+ */
+ public void setDialogContainer(StackPane dialogContainer) {
+ if(dialogContainer!=null){
+ this.dialogContainer = dialogContainer;
+ if(this.dialogContainer.getChildren().indexOf(this)==-1 || this.dialogContainer.getChildren().indexOf(this)!=this.dialogContainer.getChildren().size()-1){
+ this.dialogContainer.getChildren().remove(this);
+ this.dialogContainer.getChildren().add(this);
+ }
+ // FIXME: need to be improved to consider only the parent boundary
+ offsetX = (this.getParent().getBoundsInLocal().getWidth());
+ offsetY = (this.getParent().getBoundsInLocal().getHeight());
+ animation = getShowAnimation(transitionType.get());
+ }
+ }
+
+ /**
+ * @return dialog content node
+ */
+ public Region getContent() {
+ return content;
+ }
+
+ /**
+ * set the content of the dialog
+ * @param content
+ */
+ public void setContent(Region content) {
+ if(content!=null){
+ this.content = content;
+ this.content.setPickOnBounds(false);
+ contentHolder.getChildren().add(content);
+ }
+ }
+
+ /**
+ * indicates whether the dialog will close when clicking on the overlay or not
+ * @return
+ */
+ private BooleanProperty overlayClose = new SimpleBooleanProperty(true);
+
+ public final BooleanProperty overlayCloseProperty() {
+ return this.overlayClose;
+ }
+ public final boolean isOverlayClose() {
+ return this.overlayCloseProperty().get();
+ }
+ public final void setOverlayClose(final boolean overlayClose) {
+ this.overlayCloseProperty().set(overlayClose);
+ }
+
+ /**
+ * it will show the dialog in the specified container
+ * @param dialogContainer
+ */
+ public void show(StackPane dialogContainer){
+ this.setDialogContainer(dialogContainer);
+ animation.play();
+ }
+
+ /**
+ * show the dialog inside its parent container
+ */
+ public void show(){
+ this.setDialogContainer(dialogContainer);
+ // animation = getShowAnimation(transitionType.get());
+ animation.play();
+ }
+
+ /**
+ * close the dialog
+ */
+ public void close(){
+ animation.setRate(-1);
+ animation.play();
+ animation.setOnFinished((e)->{
+ resetProperties();
+ onDialogClosedProperty.get().handle(new JFXDialogEvent(JFXDialogEvent.CLOSED));
+ dialogContainer.getChildren().remove(this);
+ });
+ }
+
+ /***************************************************************************
+ * *
+ * Transitions *
+ * *
+ **************************************************************************/
+
+ private Transition getShowAnimation(DialogTransition transitionType){
+ Transition animation = null;
+ if(contentHolder!=null){
+ switch (transitionType) {
+ case LEFT:
+ contentHolder.setScaleX(1);
+ contentHolder.setScaleY(1);
+ contentHolder.setTranslateX(-offsetX);
+ animation = new LeftTransition();
+ break;
+ case RIGHT:
+ contentHolder.setScaleX(1);
+ contentHolder.setScaleY(1);
+ contentHolder.setTranslateX(offsetX);
+ animation = new RightTransition();
+ break;
+ case TOP:
+ contentHolder.setScaleX(1);
+ contentHolder.setScaleY(1);
+ contentHolder.setTranslateY(-offsetY);
+ animation = new TopTransition();
+ break;
+ case BOTTOM:
+ contentHolder.setScaleX(1);
+ contentHolder.setScaleY(1);
+ contentHolder.setTranslateY(offsetY);
+ animation = new BottomTransition();
+ break;
+ default:
+ contentHolder.setScaleX(0);
+ contentHolder.setScaleY(0);
+ animation = new CenterTransition();
+ break;
+ }
+ }
+ if(animation!=null)animation.setOnFinished((finish)->onDialogOpenedProperty.get().handle(new JFXDialogEvent(JFXDialogEvent.OPENED)));
+ return animation;
+ }
+
+ private void resetProperties(){
+ this.setVisible(false);
+ contentHolder.setTranslateX(0);
+ contentHolder.setTranslateY(0);
+ contentHolder.setScaleX(1);
+ contentHolder.setScaleY(1);
+ }
+
+ private class LeftTransition extends CachedTransition {
+ public LeftTransition() {
+ super(contentHolder, new Timeline(
+ new KeyFrame(Duration.ZERO,
+ new KeyValue(contentHolder.translateXProperty(), -offsetX ,Interpolator.EASE_BOTH),
+ new KeyValue(JFXDialog.this.visibleProperty(), false ,Interpolator.EASE_BOTH)
+ ),
+ new KeyFrame(Duration.millis(10),
+ new KeyValue(JFXDialog.this.visibleProperty(), true ,Interpolator.EASE_BOTH),
+ new KeyValue(JFXDialog.this.opacityProperty(), 0,Interpolator.EASE_BOTH)
+ ),
+ new KeyFrame(Duration.millis(1000),
+ new KeyValue(contentHolder.translateXProperty(), 0,Interpolator.EASE_BOTH),
+ new KeyValue(JFXDialog.this.opacityProperty(), 1,Interpolator.EASE_BOTH)
+ ))
+ );
+ // reduce the number to increase the shifting , increase number to reduce shifting
+ setCycleDuration(Duration.seconds(0.4));
+ setDelay(Duration.seconds(0));
+ }
+ }
+
+ private class RightTransition extends CachedTransition {
+ public RightTransition() {
+ super(contentHolder, new Timeline(
+ new KeyFrame(Duration.ZERO,
+ new KeyValue(contentHolder.translateXProperty(), offsetX ,Interpolator.EASE_BOTH),
+ new KeyValue(JFXDialog.this.visibleProperty(), false ,Interpolator.EASE_BOTH)
+ ),
+ new KeyFrame(Duration.millis(10),
+ new KeyValue(JFXDialog.this.visibleProperty(), true ,Interpolator.EASE_BOTH),
+ new KeyValue(JFXDialog.this.opacityProperty(), 0, Interpolator.EASE_BOTH)
+ ),
+ new KeyFrame(Duration.millis(1000),
+ new KeyValue(contentHolder.translateXProperty(), 0,Interpolator.EASE_BOTH),
+ new KeyValue(JFXDialog.this.opacityProperty(), 1, Interpolator.EASE_BOTH)))
+ );
+ // reduce the number to increase the shifting , increase number to reduce shifting
+ setCycleDuration(Duration.seconds(0.4));
+ setDelay(Duration.seconds(0));
+ }
+ }
+
+ private class TopTransition extends CachedTransition {
+ public TopTransition() {
+ super(contentHolder, new Timeline(
+ new KeyFrame(Duration.ZERO,
+ new KeyValue(contentHolder.translateYProperty(), -offsetY ,Interpolator.EASE_BOTH),
+ new KeyValue(JFXDialog.this.visibleProperty(), false ,Interpolator.EASE_BOTH)
+ ),
+ new KeyFrame(Duration.millis(10),
+ new KeyValue(JFXDialog.this.visibleProperty(), true ,Interpolator.EASE_BOTH),
+ new KeyValue(JFXDialog.this.opacityProperty(), 0, Interpolator.EASE_BOTH)
+ ),
+ new KeyFrame(Duration.millis(1000),
+ new KeyValue(contentHolder.translateYProperty(), 0,Interpolator.EASE_BOTH),
+ new KeyValue(JFXDialog.this.opacityProperty(), 1, Interpolator.EASE_BOTH)))
+ );
+ // reduce the number to increase the shifting , increase number to reduce shifting
+ setCycleDuration(Duration.seconds(0.4));
+ setDelay(Duration.seconds(0));
+ }
+ }
+
+ private class BottomTransition extends CachedTransition {
+ public BottomTransition() {
+ super(contentHolder, new Timeline(
+ new KeyFrame(Duration.ZERO,
+ new KeyValue(contentHolder.translateYProperty(), offsetY ,Interpolator.EASE_BOTH),
+ new KeyValue(JFXDialog.this.visibleProperty(), false ,Interpolator.EASE_BOTH)
+ ),
+ new KeyFrame(Duration.millis(10),
+ new KeyValue(JFXDialog.this.visibleProperty(), true ,Interpolator.EASE_BOTH),
+ new KeyValue(JFXDialog.this.opacityProperty(), 0, Interpolator.EASE_BOTH)
+ ),
+ new KeyFrame(Duration.millis(1000),
+ new KeyValue(contentHolder.translateYProperty(), 0,Interpolator.EASE_BOTH),
+ new KeyValue(JFXDialog.this.opacityProperty(), 1, Interpolator.EASE_BOTH)))
+ );
+ // reduce the number to increase the shifting , increase number to reduce shifting
+ setCycleDuration(Duration.seconds(0.4));
+ setDelay(Duration.seconds(0));
+ }
+ }
+
+ private class CenterTransition extends CachedTransition {
+ public CenterTransition() {
+ super(contentHolder, new Timeline(
+ new KeyFrame(Duration.ZERO,
+ new KeyValue(contentHolder.scaleXProperty(), 0 ,Interpolator.EASE_BOTH),
+ new KeyValue(contentHolder.scaleYProperty(), 0 ,Interpolator.EASE_BOTH),
+ new KeyValue(JFXDialog.this.visibleProperty(), false ,Interpolator.EASE_BOTH)
+ ),
+ new KeyFrame(Duration.millis(10),
+ new KeyValue(JFXDialog.this.visibleProperty(), true ,Interpolator.EASE_BOTH),
+ new KeyValue(JFXDialog.this.opacityProperty(), 0,Interpolator.EASE_BOTH)
+ ),
+ new KeyFrame(Duration.millis(1000),
+ new KeyValue(contentHolder.scaleXProperty(), 1 ,Interpolator.EASE_BOTH),
+ new KeyValue(contentHolder.scaleYProperty(), 1 ,Interpolator.EASE_BOTH),
+ new KeyValue(JFXDialog.this.opacityProperty(), 1, Interpolator.EASE_BOTH)
+ ))
+ );
+ // reduce the number to increase the shifting , increase number to reduce shifting
+ setCycleDuration(Duration.seconds(0.4));
+ setDelay(Duration.seconds(0));
+ }
+ }
+
+
+ /***************************************************************************
+ * *
+ * Stylesheet Handling *
+ * *
+ **************************************************************************/
+ /**
+ * Initialize the style class to 'jfx-dialog'.
+ *
+ * This is the selector class from which CSS can be used to style
+ * this control.
+ */
+ private static final String DEFAULT_STYLE_CLASS = "jfx-dialog";
+
+ /**
+ * dialog transition type property, it can be one of the following:
+ *
+ *
+ */
+ private StyleableObjectProperty