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; - -/** - *

Material Design PasswordField Skin for android

- * The JFXPasswordFieldSkinAndroid implements material design password field for android - * when porting JFoenix to android using JavaFXPorts - *

- * 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; + +/** + *

Material Design PasswordField Skin for android

+ * The JFXPasswordFieldSkinAndroid implements material design password field for android + * when porting JFoenix to android using JavaFXPorts + *

+ * 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; - -/** - *

Material Design TextArea Skin for android

- * The JFXTextAreaSkinAndroid implements material design text area for android - * when porting JFoenix to android using JavaFXPorts - *

- * 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; + +/** + *

Material Design TextArea Skin for android

+ * The JFXTextAreaSkinAndroid implements material design text area for android + * when porting JFoenix to android using JavaFXPorts + *

+ * 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; - -/** - *

Material Design TextField Skin for android

- * The JFXTextFieldSkinAndroid implements material design text field for android - * when porting JFoenix to android using JavaFXPorts - *

- * 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; + +/** + *

Material Design TextField Skin for android

+ * The JFXTextFieldSkinAndroid implements material design text field for android + * when porting JFoenix to android using JavaFXPorts + *

+ * 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; + + +/** + *

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(); + } + } +} 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 badgeFill = new SimpleStyleableObjectProperty( -// StyleableProperties.BADGE_FILL, JFXBadge.this, "badgeFill", Color.rgb(0, 200, 255)); -// -// public Paint getBadgeFill() { -// return badgeFill == null ? Color.rgb(0, 200, 255) : badgeFill.get(); -// } -// -// public StyleableObjectProperty badgeFillProperty() { -// return this.badgeFill; -// } -// -// public void setBadgeFill(Paint color) { -// this.badgeFill.set(color); -// } -// -// private StyleableObjectProperty badgeScale = new SimpleStyleableObjectProperty( -// StyleableProperties.BADGE_SCALE, JFXBadge.this, "badgeScale", 3d); -// -// public Number getBadgeScale() { -// return badgeScale == null ? 3d : badgeScale.get(); -// } -// -// public StyleableObjectProperty badgeScaleProperty() { -// return this.badgeScale; -// } -// -// public void setBadgeScale(Double scale) { -// this.badgeScale.set(scale); -// } -// - protected ObjectProperty position = new SimpleObjectProperty(); - - public Pos getPosition() { - return position == null ? Pos.TOP_RIGHT : position.get(); - } - - public ObjectProperty positionProperty() { - return this.position; - } - - public void setPosition(Pos position){ - this.position.set(position); - } - - private SimpleStringProperty text = new SimpleStringProperty(); - - public final String getText() { - return text.get(); - } - - public final void setText(String value) { - text.set(value); - } - - public final StringProperty textProperty() { - return text; - } - -// private static class StyleableProperties { -// private static final CssMetaData BADGE_FILL = new CssMetaData( -// "-fx-badge-fill", PaintConverter.getInstance(), Color.rgb(0, 200, 255)) { -// @Override -// public boolean isSettable(JFXBadge control) { -// return control.badgeFill == null || !control.badgeFill.isBound(); -// } -// -// @Override -// public StyleableProperty getStyleableProperty(JFXBadge control) { -// return control.badgeFillProperty(); -// } -// }; -// -// private static final CssMetaData BADGE_SCALE = new CssMetaData( -// "-fx-badge-scale", StyleConverter.getSizeConverter(), 3d) { -// @Override -// public boolean isSettable(JFXBadge control) { -// return control.badgeScale == null || !control.badgeScale.isBound(); -// } -// -// @Override -// public StyleableProperty getStyleableProperty(JFXBadge control) { -// return (StyleableProperty) control.badgeScaleProperty(); -// } -// }; -// -// -// private static final List> STYLEABLES; -// -// static { -// final List> styleables = new ArrayList>( -// Parent.getClassCssMetaData()); -// Collections.addAll(styleables, BADGE_FILL, BADGE_SCALE); -// STYLEABLES = Collections.unmodifiableList(styleables); -// } -// } -// -// @Override -// public List> getCssMetaData() { -// return getClassCssMetaData(); -// } -// -// public static List> getClassCssMetaData() { -// return StyleableProperties.STYLEABLES; -// } - -} +/* + * 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 badgeFill = new SimpleStyleableObjectProperty( +// StyleableProperties.BADGE_FILL, JFXBadge.this, "badgeFill", Color.rgb(0, 200, 255)); +// +// public Paint getBadgeFill() { +// return badgeFill == null ? Color.rgb(0, 200, 255) : badgeFill.get(); +// } +// +// public StyleableObjectProperty badgeFillProperty() { +// return this.badgeFill; +// } +// +// public void setBadgeFill(Paint color) { +// this.badgeFill.set(color); +// } +// +// private StyleableObjectProperty badgeScale = new SimpleStyleableObjectProperty( +// StyleableProperties.BADGE_SCALE, JFXBadge.this, "badgeScale", 3d); +// +// public Number getBadgeScale() { +// return badgeScale == null ? 3d : badgeScale.get(); +// } +// +// public StyleableObjectProperty badgeScaleProperty() { +// return this.badgeScale; +// } +// +// public void setBadgeScale(Double scale) { +// this.badgeScale.set(scale); +// } +// + protected ObjectProperty position = new SimpleObjectProperty(); + + public Pos getPosition() { + return position == null ? Pos.TOP_RIGHT : position.get(); + } + + public ObjectProperty positionProperty() { + return this.position; + } + + public void setPosition(Pos position){ + this.position.set(position); + } + + private SimpleStringProperty text = new SimpleStringProperty(); + + public final String getText() { + return text.get(); + } + + public final void setText(String value) { + text.set(value); + } + + public final StringProperty textProperty() { + return text; + } + +// private static class StyleableProperties { +// private static final CssMetaData BADGE_FILL = new CssMetaData( +// "-fx-badge-fill", PaintConverter.getInstance(), Color.rgb(0, 200, 255)) { +// @Override +// public boolean isSettable(JFXBadge control) { +// return control.badgeFill == null || !control.badgeFill.isBound(); +// } +// +// @Override +// public StyleableProperty getStyleableProperty(JFXBadge control) { +// return control.badgeFillProperty(); +// } +// }; +// +// private static final CssMetaData BADGE_SCALE = new CssMetaData( +// "-fx-badge-scale", StyleConverter.getSizeConverter(), 3d) { +// @Override +// public boolean isSettable(JFXBadge control) { +// return control.badgeScale == null || !control.badgeScale.isBound(); +// } +// +// @Override +// public StyleableProperty getStyleableProperty(JFXBadge control) { +// return (StyleableProperty) control.badgeScaleProperty(); +// } +// }; +// +// +// private static final List> STYLEABLES; +// +// static { +// final List> styleables = new ArrayList>( +// Parent.getClassCssMetaData()); +// Collections.addAll(styleables, BADGE_FILL, BADGE_SCALE); +// STYLEABLES = Collections.unmodifiableList(styleables); +// } +// } +// +// @Override +// public List> getCssMetaData() { +// return getClassCssMetaData(); +// } +// +// public static List> getClassCssMetaData() { +// return StyleableProperties.STYLEABLES; +// } + +} diff --git a/src/com/jfoenix/controls/JFXButton.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXButton.java similarity index 97% rename from src/com/jfoenix/controls/JFXButton.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXButton.java index 6836b0ca..d166d6ff 100644 --- a/src/com/jfoenix/controls/JFXButton.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/JFXButton.java @@ -1,198 +1,198 @@ -/* - * 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 com.jfoenix.converters.ButtonTypeConverter; -import com.jfoenix.skins.JFXButtonSkin; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.css.*; -import javafx.scene.Node; -import javafx.scene.control.Button; -import javafx.scene.control.Control; -import javafx.scene.control.Skin; -import javafx.scene.paint.Paint; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * JFXButton is the material design implementation of a button. - * it contains ripple effect , the effect color is set according to text fill of the button 1st - * or the text fill of graphic node (if it was set to Label) 2nd. - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXButton extends Button { - - /** - * {@inheritDoc} - */ - public JFXButton() { - super(); - initialize(); - // init in scene builder workaround ( TODO : remove when JFoenix is well integrated in scenebuilder by gluon ) - StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); - for(int i = 0 ; i < stackTraceElements.length && i < 15; i++){ - if(stackTraceElements[i].getClassName().toLowerCase().contains(".scenebuilder.kit.fxom.")){ - this.setText("Button"); - break; - } - } - } - /** - * {@inheritDoc} - */ - public JFXButton(String text){ - super(text); - initialize(); - } - /** - * {@inheritDoc} - */ - public JFXButton(String text, Node graphic){ - super(text, graphic); - initialize(); - } - - private void initialize() { - this.getStyleClass().add(DEFAULT_STYLE_CLASS); - } - - /** - * {@inheritDoc} - */ - @Override - protected Skin createDefaultSkin() { - return new JFXButtonSkin(this); - } - - - /*************************************************************************** - * * - * Properties * - * * - **************************************************************************/ - /** - * the ripple color property of JFXButton. - */ - private ObjectProperty ripplerFill = new SimpleObjectProperty<>(null); - - public final ObjectProperty ripplerFillProperty() { - return this.ripplerFill; - } - - /** - * @return the ripple color - */ - public final Paint getRipplerFill() { - return this.ripplerFillProperty().get(); - } - - /** - * set the ripple color - * @param ripplerFill the color of the ripple effect - */ - public final void setRipplerFill(final Paint ripplerFill) { - this.ripplerFillProperty().set(ripplerFill); - } - - - /*************************************************************************** - * * - * Stylesheet Handling * - * * - **************************************************************************/ - - /** - * Initialize the style class to 'jfx-button'. - * - * This is the selector class from which CSS can be used to style - * this control. - */ - private static final String DEFAULT_STYLE_CLASS = "jfx-button"; - - - public static enum ButtonType{FLAT, RAISED}; - - /** - * according to material design the button has two types: - * - flat : only shows the ripple effect upon clicking the button - * - raised : shows the ripple effect and change in depth upon clicking the button - */ - private StyleableObjectProperty buttonType = new SimpleStyleableObjectProperty(StyleableProperties.BUTTON_TYPE, JFXButton.this, "buttonType", ButtonType.FLAT ); - - public ButtonType getButtonType(){ - return buttonType == null ? ButtonType.FLAT : buttonType.get(); - } - public StyleableObjectProperty buttonTypeProperty(){ - return this.buttonType; - } - public void setButtonType(ButtonType type){ - this.buttonType.set(type); - } - - private static class StyleableProperties { - private static final CssMetaData< JFXButton, ButtonType> BUTTON_TYPE = - new CssMetaData< JFXButton, ButtonType>("-jfx-button-type", - ButtonTypeConverter.getInstance(), ButtonType.FLAT) { - @Override - public boolean isSettable(JFXButton control) { - return control.buttonType == null || !control.buttonType.isBound(); - } - @Override - public StyleableProperty getStyleableProperty(JFXButton control) { - return control.buttonTypeProperty(); - } - }; - - private static final List> CHILD_STYLEABLES; - static { - final List> styleables = - new ArrayList>(Control.getClassCssMetaData()); - Collections.addAll(styleables, - BUTTON_TYPE - ); - CHILD_STYLEABLES = Collections.unmodifiableList(styleables); - } - } - - // inherit the styleable properties from parent - private List> STYLEABLES; - - @Override - public List> getControlCssMetaData() { - if(STYLEABLES == null){ - final List> styleables = - new ArrayList>(Control.getClassCssMetaData()); - styleables.addAll(getClassCssMetaData()); - styleables.addAll(super.getClassCssMetaData()); - STYLEABLES = Collections.unmodifiableList(styleables); - } - return STYLEABLES; - } - public static List> getClassCssMetaData() { - return StyleableProperties.CHILD_STYLEABLES; - } - - -} +/* + * 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 com.jfoenix.converters.ButtonTypeConverter; +import com.jfoenix.skins.JFXButtonSkin; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.css.*; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.Control; +import javafx.scene.control.Skin; +import javafx.scene.paint.Paint; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * JFXButton is the material design implementation of a button. + * it contains ripple effect , the effect color is set according to text fill of the button 1st + * or the text fill of graphic node (if it was set to Label) 2nd. + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXButton extends Button { + + /** + * {@inheritDoc} + */ + public JFXButton() { + super(); + initialize(); + // init in scene builder workaround ( TODO : remove when JFoenix is well integrated in scenebuilder by gluon ) + StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); + for(int i = 0 ; i < stackTraceElements.length && i < 15; i++){ + if(stackTraceElements[i].getClassName().toLowerCase().contains(".scenebuilder.kit.fxom.")){ + this.setText("Button"); + break; + } + } + } + /** + * {@inheritDoc} + */ + public JFXButton(String text){ + super(text); + initialize(); + } + /** + * {@inheritDoc} + */ + public JFXButton(String text, Node graphic){ + super(text, graphic); + initialize(); + } + + private void initialize() { + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + } + + /** + * {@inheritDoc} + */ + @Override + protected Skin createDefaultSkin() { + return new JFXButtonSkin(this); + } + + + /*************************************************************************** + * * + * Properties * + * * + **************************************************************************/ + /** + * the ripple color property of JFXButton. + */ + private ObjectProperty ripplerFill = new SimpleObjectProperty<>(null); + + public final ObjectProperty ripplerFillProperty() { + return this.ripplerFill; + } + + /** + * @return the ripple color + */ + public final Paint getRipplerFill() { + return this.ripplerFillProperty().get(); + } + + /** + * set the ripple color + * @param ripplerFill the color of the ripple effect + */ + public final void setRipplerFill(final Paint ripplerFill) { + this.ripplerFillProperty().set(ripplerFill); + } + + + /*************************************************************************** + * * + * Stylesheet Handling * + * * + **************************************************************************/ + + /** + * Initialize the style class to 'jfx-button'. + * + * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-button"; + + + public static enum ButtonType{FLAT, RAISED}; + + /** + * according to material design the button has two types: + * - flat : only shows the ripple effect upon clicking the button + * - raised : shows the ripple effect and change in depth upon clicking the button + */ + private StyleableObjectProperty buttonType = new SimpleStyleableObjectProperty(StyleableProperties.BUTTON_TYPE, JFXButton.this, "buttonType", ButtonType.FLAT ); + + public ButtonType getButtonType(){ + return buttonType == null ? ButtonType.FLAT : buttonType.get(); + } + public StyleableObjectProperty buttonTypeProperty(){ + return this.buttonType; + } + public void setButtonType(ButtonType type){ + this.buttonType.set(type); + } + + private static class StyleableProperties { + private static final CssMetaData< JFXButton, ButtonType> BUTTON_TYPE = + new CssMetaData< JFXButton, ButtonType>("-jfx-button-type", + ButtonTypeConverter.getInstance(), ButtonType.FLAT) { + @Override + public boolean isSettable(JFXButton control) { + return control.buttonType == null || !control.buttonType.isBound(); + } + @Override + public StyleableProperty getStyleableProperty(JFXButton control) { + return control.buttonTypeProperty(); + } + }; + + private static final List> CHILD_STYLEABLES; + static { + final List> styleables = + new ArrayList>(Control.getClassCssMetaData()); + Collections.addAll(styleables, + BUTTON_TYPE + ); + CHILD_STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + // inherit the styleable properties from parent + private List> STYLEABLES; + + @Override + public List> getControlCssMetaData() { + if(STYLEABLES == null){ + final List> styleables = + new ArrayList>(Control.getClassCssMetaData()); + styleables.addAll(getClassCssMetaData()); + styleables.addAll(super.getClassCssMetaData()); + STYLEABLES = Collections.unmodifiableList(styleables); + } + return STYLEABLES; + } + public static List> getClassCssMetaData() { + return StyleableProperties.CHILD_STYLEABLES; + } + + +} diff --git a/src/com/jfoenix/controls/JFXCheckBox.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXCheckBox.java similarity index 97% rename from src/com/jfoenix/controls/JFXCheckBox.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXCheckBox.java index a415604e..97ec2f59 100644 --- a/src/com/jfoenix/controls/JFXCheckBox.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/JFXCheckBox.java @@ -1,184 +1,184 @@ -/* - * 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 com.jfoenix.skins.JFXCheckBoxSkin; -import com.sun.javafx.css.converters.PaintConverter; -import javafx.css.*; -import javafx.scene.control.CheckBox; -import javafx.scene.control.Control; -import javafx.scene.control.Skin; -import javafx.scene.paint.Color; -import javafx.scene.paint.Paint; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * JFXCheckBox is the material design implementation of a checkbox. - * it shows ripple effect and a custom selection animation. - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXCheckBox extends CheckBox { - - /** - * {@inheritDoc} - */ - public JFXCheckBox(String text){ - super(text); - initialize(); - } - - /** - * {@inheritDoc} - */ - public JFXCheckBox(){ - super(); - initialize(); - // init in scene builder workaround ( TODO : remove when JFoenix is well integrated in scenebuilder by gluon ) - StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); - for(int i = 0 ; i < stackTraceElements.length && i < 15; i++){ - if(stackTraceElements[i].getClassName().toLowerCase().contains(".scenebuilder.kit.fxom.")){ - this.setText("CheckBox"); - break; - } - } - } - - private void initialize() { - this.getStyleClass().add(DEFAULT_STYLE_CLASS); - } - - /** - * {@inheritDoc} - */ - @Override - protected Skin createDefaultSkin() { - return new JFXCheckBoxSkin(this); - } - - - /*************************************************************************** - * * - * Stylesheet Handling * - * * - **************************************************************************/ - - /** - * Initialize the style class to 'jfx-check-box'. - * - * This is the selector class from which CSS can be used to style - * this control. - */ - private static final String DEFAULT_STYLE_CLASS = "jfx-check-box"; - - /** - * checkbox color property when selected - */ - private StyleableObjectProperty checkedColor = new SimpleStyleableObjectProperty(StyleableProperties.CHECKED_COLOR, JFXCheckBox.this, "checkedColor", Color.valueOf("#0F9D58")); - - public Paint getCheckedColor(){ - return checkedColor == null ? Color.valueOf("#0F9D58") : checkedColor.get(); - } - public StyleableObjectProperty checkedColorProperty(){ - return this.checkedColor; - } - public void setCheckedColor(Paint color){ - this.checkedColor.set(color); - } - - /** - * checkbox color property when not selected - */ - private StyleableObjectProperty unCheckedColor = new SimpleStyleableObjectProperty(StyleableProperties.UNCHECKED_COLOR, JFXCheckBox.this, "unCheckedColor", Color.valueOf("#5A5A5A")); - - public Paint getUnCheckedColor(){ - return unCheckedColor == null ? Color.valueOf("#5A5A5A") : unCheckedColor.get(); - } - public StyleableObjectProperty unCheckedColorProperty(){ - return this.unCheckedColor; - } - public void setUnCheckedColor(Paint color){ - this.unCheckedColor.set(color); - } - - - private static class StyleableProperties { - private static final CssMetaData< JFXCheckBox, Paint> CHECKED_COLOR = - new CssMetaData< JFXCheckBox, Paint>("-jfx-checked-color", - PaintConverter.getInstance(), Color.valueOf("#0F9D58")) { - @Override - public boolean isSettable(JFXCheckBox control) { - return control.checkedColor == null || !control.checkedColor.isBound(); - } - @Override - public StyleableProperty getStyleableProperty(JFXCheckBox control) { - return control.checkedColorProperty(); - } - }; - private static final CssMetaData< JFXCheckBox, Paint> UNCHECKED_COLOR = - new CssMetaData< JFXCheckBox, Paint>("-jfx-unchecked-color", - PaintConverter.getInstance(), Color.valueOf("#5A5A5A")) { - @Override - public boolean isSettable(JFXCheckBox control) { - return control.unCheckedColor == null || !control.unCheckedColor.isBound(); - } - @Override - public StyleableProperty getStyleableProperty(JFXCheckBox control) { - return control.unCheckedColorProperty(); - } - }; - - private static final List> CHILD_STYLEABLES; - static { - final List> styleables = - new ArrayList>(Control.getClassCssMetaData()); - Collections.addAll(styleables, - CHECKED_COLOR, - UNCHECKED_COLOR - ); - CHILD_STYLEABLES = Collections.unmodifiableList(styleables); - } - } - - // inherit the styleable properties from parent - private List> STYLEABLES; - - @Override - public List> getControlCssMetaData() { - if(STYLEABLES == null){ - final List> styleables = - new ArrayList>(Control.getClassCssMetaData()); - styleables.addAll(getClassCssMetaData()); - styleables.addAll(super.getClassCssMetaData()); - STYLEABLES = Collections.unmodifiableList(styleables); - } - return STYLEABLES; - } - public static List> getClassCssMetaData() { - return StyleableProperties.CHILD_STYLEABLES; - } - - - +/* + * 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 com.jfoenix.skins.JFXCheckBoxSkin; +import com.sun.javafx.css.converters.PaintConverter; +import javafx.css.*; +import javafx.scene.control.CheckBox; +import javafx.scene.control.Control; +import javafx.scene.control.Skin; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * JFXCheckBox is the material design implementation of a checkbox. + * it shows ripple effect and a custom selection animation. + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXCheckBox extends CheckBox { + + /** + * {@inheritDoc} + */ + public JFXCheckBox(String text){ + super(text); + initialize(); + } + + /** + * {@inheritDoc} + */ + public JFXCheckBox(){ + super(); + initialize(); + // init in scene builder workaround ( TODO : remove when JFoenix is well integrated in scenebuilder by gluon ) + StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); + for(int i = 0 ; i < stackTraceElements.length && i < 15; i++){ + if(stackTraceElements[i].getClassName().toLowerCase().contains(".scenebuilder.kit.fxom.")){ + this.setText("CheckBox"); + break; + } + } + } + + private void initialize() { + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + } + + /** + * {@inheritDoc} + */ + @Override + protected Skin createDefaultSkin() { + return new JFXCheckBoxSkin(this); + } + + + /*************************************************************************** + * * + * Stylesheet Handling * + * * + **************************************************************************/ + + /** + * Initialize the style class to 'jfx-check-box'. + * + * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-check-box"; + + /** + * checkbox color property when selected + */ + private StyleableObjectProperty checkedColor = new SimpleStyleableObjectProperty(StyleableProperties.CHECKED_COLOR, JFXCheckBox.this, "checkedColor", Color.valueOf("#0F9D58")); + + public Paint getCheckedColor(){ + return checkedColor == null ? Color.valueOf("#0F9D58") : checkedColor.get(); + } + public StyleableObjectProperty checkedColorProperty(){ + return this.checkedColor; + } + public void setCheckedColor(Paint color){ + this.checkedColor.set(color); + } + + /** + * checkbox color property when not selected + */ + private StyleableObjectProperty unCheckedColor = new SimpleStyleableObjectProperty(StyleableProperties.UNCHECKED_COLOR, JFXCheckBox.this, "unCheckedColor", Color.valueOf("#5A5A5A")); + + public Paint getUnCheckedColor(){ + return unCheckedColor == null ? Color.valueOf("#5A5A5A") : unCheckedColor.get(); + } + public StyleableObjectProperty unCheckedColorProperty(){ + return this.unCheckedColor; + } + public void setUnCheckedColor(Paint color){ + this.unCheckedColor.set(color); + } + + + private static class StyleableProperties { + private static final CssMetaData< JFXCheckBox, Paint> CHECKED_COLOR = + new CssMetaData< JFXCheckBox, Paint>("-jfx-checked-color", + PaintConverter.getInstance(), Color.valueOf("#0F9D58")) { + @Override + public boolean isSettable(JFXCheckBox control) { + return control.checkedColor == null || !control.checkedColor.isBound(); + } + @Override + public StyleableProperty getStyleableProperty(JFXCheckBox control) { + return control.checkedColorProperty(); + } + }; + private static final CssMetaData< JFXCheckBox, Paint> UNCHECKED_COLOR = + new CssMetaData< JFXCheckBox, Paint>("-jfx-unchecked-color", + PaintConverter.getInstance(), Color.valueOf("#5A5A5A")) { + @Override + public boolean isSettable(JFXCheckBox control) { + return control.unCheckedColor == null || !control.unCheckedColor.isBound(); + } + @Override + public StyleableProperty getStyleableProperty(JFXCheckBox control) { + return control.unCheckedColorProperty(); + } + }; + + private static final List> CHILD_STYLEABLES; + static { + final List> styleables = + new ArrayList>(Control.getClassCssMetaData()); + Collections.addAll(styleables, + CHECKED_COLOR, + UNCHECKED_COLOR + ); + CHILD_STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + // inherit the styleable properties from parent + private List> STYLEABLES; + + @Override + public List> getControlCssMetaData() { + if(STYLEABLES == null){ + final List> styleables = + new ArrayList>(Control.getClassCssMetaData()); + styleables.addAll(getClassCssMetaData()); + styleables.addAll(super.getClassCssMetaData()); + STYLEABLES = Collections.unmodifiableList(styleables); + } + return STYLEABLES; + } + public static List> getClassCssMetaData() { + return StyleableProperties.CHILD_STYLEABLES; + } + + + } \ No newline at end of file diff --git a/src/com/jfoenix/controls/JFXColorPicker.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXColorPicker.java similarity index 96% rename from src/com/jfoenix/controls/JFXColorPicker.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXColorPicker.java index 285ff27c..f9d32477 100644 --- a/src/com/jfoenix/controls/JFXColorPicker.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/JFXColorPicker.java @@ -1,70 +1,70 @@ -/* - * 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 com.jfoenix.skins.JFXColorPickerSkin; -import javafx.scene.control.ColorPicker; -import javafx.scene.control.Skin; -import javafx.scene.paint.Color; - -/** - * JFXColorPicker is the metrial design implementation of color picker. - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXColorPicker extends ColorPicker { - - /** - * {@inheritDoc} - */ - public JFXColorPicker() { - super(); - initialize(); - } - - /** - * {@inheritDoc} - */ - public JFXColorPicker(Color color) { - super(color); - initialize(); - } - - /** - * {@inheritDoc} - */ - @Override protected Skin createDefaultSkin() { - return new JFXColorPickerSkin(this); - } - - private void initialize() { - this.getStyleClass().add(DEFAULT_STYLE_CLASS); - } - - /** - * Initialize the style class to 'jfx-color-picker'. - * - * This is the selector class from which CSS can be used to style - * this control. - */ - private static final String DEFAULT_STYLE_CLASS = "jfx-color-picker"; - -} +/* + * 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 com.jfoenix.skins.JFXColorPickerSkin; +import javafx.scene.control.ColorPicker; +import javafx.scene.control.Skin; +import javafx.scene.paint.Color; + +/** + * JFXColorPicker is the metrial design implementation of color picker. + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXColorPicker extends ColorPicker { + + /** + * {@inheritDoc} + */ + public JFXColorPicker() { + super(); + initialize(); + } + + /** + * {@inheritDoc} + */ + public JFXColorPicker(Color color) { + super(color); + initialize(); + } + + /** + * {@inheritDoc} + */ + @Override protected Skin createDefaultSkin() { + return new JFXColorPickerSkin(this); + } + + private void initialize() { + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + } + + /** + * Initialize the style class to 'jfx-color-picker'. + * + * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-color-picker"; + +} diff --git a/src/com/jfoenix/controls/JFXComboBox.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXComboBox.java similarity index 97% rename from src/com/jfoenix/controls/JFXComboBox.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXComboBox.java index 8350562f..d0632739 100644 --- a/src/com/jfoenix/controls/JFXComboBox.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/JFXComboBox.java @@ -1,324 +1,324 @@ -/* - * 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 java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import com.jfoenix.converters.base.NodeConverter; -import com.jfoenix.skins.JFXComboBoxListViewSkin; -import com.sun.javafx.css.converters.BooleanConverter; -import com.sun.javafx.css.converters.PaintConverter; - -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.collections.ObservableList; -import javafx.css.CssMetaData; -import javafx.css.SimpleStyleableBooleanProperty; -import javafx.css.SimpleStyleableObjectProperty; -import javafx.css.Styleable; -import javafx.css.StyleableBooleanProperty; -import javafx.css.StyleableObjectProperty; -import javafx.css.StyleableProperty; -import javafx.geometry.Insets; -import javafx.geometry.Pos; -import javafx.scene.Node; -import javafx.scene.control.ComboBox; -import javafx.scene.control.Control; -import javafx.scene.control.Label; -import javafx.scene.control.ListCell; -import javafx.scene.control.ListView; -import javafx.scene.control.Skin; -import javafx.scene.layout.Background; -import javafx.scene.layout.BackgroundFill; -import javafx.scene.layout.StackPane; -import javafx.scene.paint.Color; -import javafx.scene.paint.Paint; -import javafx.util.Callback; -import javafx.util.StringConverter; - -/** - * JFXComboBox is the material design implementation of a combobox. - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXComboBox extends ComboBox { - - /** - * {@inheritDoc} - */ - public JFXComboBox() { - super(); - initialize(); - } - - /** - * {@inheritDoc} - */ - public JFXComboBox(ObservableList items) { - super(items); - initialize(); - } - - private void initialize() { - getStyleClass().add(DEFAULT_STYLE_CLASS); - this.setCellFactory(new Callback, ListCell>() { - @Override - public ListCell call(ListView listView) { - return new JFXListCell(); - } - }); - this.setConverter(new StringConverter() { - @Override - public String toString(T object) { - if(object == null) return null; - if(object instanceof Label) return ((Label)object).getText(); - return object.toString(); - } - @SuppressWarnings("unchecked") - @Override - public T fromString(String string) { - return (T) string; - } - }); - /* - * had to refactor the code out skin class to allow - * customization of the button cell - */ - this.setButtonCell(new ListCell(){ - protected void updateItem(T item, boolean empty) { - updateDisplayText(this, item, empty); - this.setVisible(item!=null || !empty); - }; - }); - } - - /** - * {@inheritDoc} - */ - @Override protected Skin createDefaultSkin() { - return new JFXComboBoxListViewSkin(this); - } - - /** - * Initialize the style class to 'jfx-combo-box'. - * - * This is the selector class from which CSS can be used to style - * this control. - */ - private static final String DEFAULT_STYLE_CLASS = "jfx-combo-box"; - - /*************************************************************************** - * * - * Node Converter Property * - * * - **************************************************************************/ - /** - * Converts the user-typed input (when the ComboBox is - * {@link #editableProperty() editable}) to an object of type T, such that - * the input may be retrieved via the {@link #valueProperty() value} property. - */ - public ObjectProperty> nodeConverterProperty() { return nodeConverter; } - private ObjectProperty> nodeConverter = new SimpleObjectProperty>(this, "nodeConverter", JFXComboBox.defaultNodeConverter()); - public final void setNodeConverter(NodeConverter value) { nodeConverterProperty().set(value); } - public final NodeConverter getNodeConverter() {return nodeConverterProperty().get(); } - - private static NodeConverter defaultNodeConverter() { - return new NodeConverter() { - @Override public Node toNode(T object) { - if(object == null) return null; - StackPane selectedValueContainer = new StackPane(); - selectedValueContainer.getStyleClass().add("combo-box-selected-value-container"); - selectedValueContainer.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, null, null))); - Label selectedValueLabel; - if(object instanceof Label) selectedValueLabel = new Label(((Label)object).getText()); - else selectedValueLabel = new Label(object.toString()); - selectedValueLabel.setTextFill(Color.BLACK); - selectedValueContainer.getChildren().add(selectedValueLabel); - StackPane.setAlignment(selectedValueLabel, Pos.CENTER_LEFT); - StackPane.setMargin(selectedValueLabel, new Insets(0,0,0,5)); - return selectedValueContainer; - } - @SuppressWarnings("unchecked") - @Override public T fromNode(Node node) { - return (T) node; - } - @Override - public String toString(T object) { - if(object == null) return null; - if(object instanceof Label) return ((Label)object).getText(); - return object.toString(); - } - }; - } - - private boolean updateDisplayText(ListCell cell, T item, boolean empty) { - if (empty) { - // create empty cell - if (cell == null) return true; - cell.setGraphic(null); - cell.setText(null); - return true; - } else if (item instanceof Node) { - Node currentNode = cell.getGraphic(); - Node newNode = (Node) item; - /* - * create a node from the selected node of the listview - * using JFXComboBox {@link #nodeConverterProperty() NodeConverter}) - */ - NodeConverter nc = this.getNodeConverter(); - Node node = nc == null? null : nc.toNode(item); - if (currentNode == null || ! currentNode.equals(newNode)) { - cell.setText(null); - cell.setGraphic(node==null? newNode : node); - } - return node == null; - } else { - // run item through StringConverter if it isn't null - StringConverter c = this.getConverter(); - String s = item == null ? this.getPromptText() : (c == null ? item.toString() : c.toString(item)); - cell.setText(s); - cell.setGraphic(null); - return s == null || s.isEmpty(); - } - } - - /*************************************************************************** - * * - * styleable Properties * - * * - **************************************************************************/ - - /** - * set true to show a float the prompt text when focusing the field - */ - private StyleableBooleanProperty labelFloat = new SimpleStyleableBooleanProperty(StyleableProperties.LABEL_FLOAT, JFXComboBox.this, "lableFloat", false); - - public final StyleableBooleanProperty labelFloatProperty() { - return this.labelFloat; - } - - public final boolean isLabelFloat() { - return this.labelFloatProperty().get(); - } - - public final void setLabelFloat(final boolean labelFloat) { - this.labelFloatProperty().set(labelFloat); - } - - /** - * default color used when the field is unfocused - */ - private StyleableObjectProperty unFocusColor = new SimpleStyleableObjectProperty(StyleableProperties.UNFOCUS_COLOR, JFXComboBox.this, "unFocusColor", Color.rgb(77, 77, 77)); - - public Paint getUnFocusColor() { - return unFocusColor == null ? Color.rgb(77, 77, 77) : unFocusColor.get(); - } - - public StyleableObjectProperty unFocusColorProperty() { - return this.unFocusColor; - } - - public void setUnFocusColor(Paint color) { - this.unFocusColor.set(color); - } - - /** - * default color used when the field is focused - */ - private StyleableObjectProperty focusColor = new SimpleStyleableObjectProperty(StyleableProperties.FOCUS_COLOR, JFXComboBox.this, "focusColor", Color.valueOf("#4059A9")); - - public Paint getFocusColor() { - return focusColor == null ? Color.valueOf("#4059A9") : focusColor.get(); - } - - public StyleableObjectProperty focusColorProperty() { - return this.focusColor; - } - - public void setFocusColor(Paint color) { - this.focusColor.set(color); - } - - - private static class StyleableProperties { - private static final CssMetaData, Paint> UNFOCUS_COLOR = new CssMetaData, Paint>("-jfx-unfocus-color", PaintConverter.getInstance(), Color.valueOf("#A6A6A6")) { - @Override - public boolean isSettable(JFXComboBox control) { - return control.unFocusColor == null || !control.unFocusColor.isBound(); - } - - @Override - public StyleableProperty getStyleableProperty(JFXComboBox control) { - return control.unFocusColorProperty(); - } - }; - private static final CssMetaData, Paint> FOCUS_COLOR = new CssMetaData, Paint>("-jfx-focus-color", PaintConverter.getInstance(), Color.valueOf("#3f51b5")) { - @Override - public boolean isSettable(JFXComboBox control) { - return control.focusColor == null || !control.focusColor.isBound(); - } - - @Override - public StyleableProperty getStyleableProperty(JFXComboBox control) { - return control.focusColorProperty(); - } - }; - private static final CssMetaData, Boolean> LABEL_FLOAT = new CssMetaData, Boolean>("-jfx-label-float", BooleanConverter.getInstance(), false) { - @Override - public boolean isSettable(JFXComboBox control) { - return control.labelFloat == null || !control.labelFloat.isBound(); - } - - @Override - public StyleableBooleanProperty getStyleableProperty(JFXComboBox control) { - return control.labelFloatProperty(); - } - }; - - - private static final List> CHILD_STYLEABLES; - static { - final List> styleables = new ArrayList>(Control.getClassCssMetaData()); - Collections.addAll(styleables, UNFOCUS_COLOR, FOCUS_COLOR, LABEL_FLOAT); - CHILD_STYLEABLES = Collections.unmodifiableList(styleables); - } - } - - // inherit the styleable properties from parent - private List> STYLEABLES; - - @Override - public List> getControlCssMetaData() { - if (STYLEABLES == null) { - final List> styleables = new ArrayList>(Control.getClassCssMetaData()); - styleables.addAll(getClassCssMetaData()); - styleables.addAll(super.getClassCssMetaData()); - STYLEABLES = Collections.unmodifiableList(styleables); - } - return STYLEABLES; - } - - public static List> getClassCssMetaData() { - return StyleableProperties.CHILD_STYLEABLES; - } -} +/* + * 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 java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.jfoenix.converters.base.NodeConverter; +import com.jfoenix.skins.JFXComboBoxListViewSkin; +import com.sun.javafx.css.converters.BooleanConverter; +import com.sun.javafx.css.converters.PaintConverter; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.ObservableList; +import javafx.css.CssMetaData; +import javafx.css.SimpleStyleableBooleanProperty; +import javafx.css.SimpleStyleableObjectProperty; +import javafx.css.Styleable; +import javafx.css.StyleableBooleanProperty; +import javafx.css.StyleableObjectProperty; +import javafx.css.StyleableProperty; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Control; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.control.Skin; +import javafx.scene.layout.Background; +import javafx.scene.layout.BackgroundFill; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; +import javafx.util.Callback; +import javafx.util.StringConverter; + +/** + * JFXComboBox is the material design implementation of a combobox. + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXComboBox extends ComboBox { + + /** + * {@inheritDoc} + */ + public JFXComboBox() { + super(); + initialize(); + } + + /** + * {@inheritDoc} + */ + public JFXComboBox(ObservableList items) { + super(items); + initialize(); + } + + private void initialize() { + getStyleClass().add(DEFAULT_STYLE_CLASS); + this.setCellFactory(new Callback, ListCell>() { + @Override + public ListCell call(ListView listView) { + return new JFXListCell(); + } + }); + this.setConverter(new StringConverter() { + @Override + public String toString(T object) { + if(object == null) return null; + if(object instanceof Label) return ((Label)object).getText(); + return object.toString(); + } + @SuppressWarnings("unchecked") + @Override + public T fromString(String string) { + return (T) string; + } + }); + /* + * had to refactor the code out skin class to allow + * customization of the button cell + */ + this.setButtonCell(new ListCell(){ + protected void updateItem(T item, boolean empty) { + updateDisplayText(this, item, empty); + this.setVisible(item!=null || !empty); + }; + }); + } + + /** + * {@inheritDoc} + */ + @Override protected Skin createDefaultSkin() { + return new JFXComboBoxListViewSkin(this); + } + + /** + * Initialize the style class to 'jfx-combo-box'. + * + * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-combo-box"; + + /*************************************************************************** + * * + * Node Converter Property * + * * + **************************************************************************/ + /** + * Converts the user-typed input (when the ComboBox is + * {@link #editableProperty() editable}) to an object of type T, such that + * the input may be retrieved via the {@link #valueProperty() value} property. + */ + public ObjectProperty> nodeConverterProperty() { return nodeConverter; } + private ObjectProperty> nodeConverter = new SimpleObjectProperty>(this, "nodeConverter", JFXComboBox.defaultNodeConverter()); + public final void setNodeConverter(NodeConverter value) { nodeConverterProperty().set(value); } + public final NodeConverter getNodeConverter() {return nodeConverterProperty().get(); } + + private static NodeConverter defaultNodeConverter() { + return new NodeConverter() { + @Override public Node toNode(T object) { + if(object == null) return null; + StackPane selectedValueContainer = new StackPane(); + selectedValueContainer.getStyleClass().add("combo-box-selected-value-container"); + selectedValueContainer.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, null, null))); + Label selectedValueLabel; + if(object instanceof Label) selectedValueLabel = new Label(((Label)object).getText()); + else selectedValueLabel = new Label(object.toString()); + selectedValueLabel.setTextFill(Color.BLACK); + selectedValueContainer.getChildren().add(selectedValueLabel); + StackPane.setAlignment(selectedValueLabel, Pos.CENTER_LEFT); + StackPane.setMargin(selectedValueLabel, new Insets(0,0,0,5)); + return selectedValueContainer; + } + @SuppressWarnings("unchecked") + @Override public T fromNode(Node node) { + return (T) node; + } + @Override + public String toString(T object) { + if(object == null) return null; + if(object instanceof Label) return ((Label)object).getText(); + return object.toString(); + } + }; + } + + private boolean updateDisplayText(ListCell cell, T item, boolean empty) { + if (empty) { + // create empty cell + if (cell == null) return true; + cell.setGraphic(null); + cell.setText(null); + return true; + } else if (item instanceof Node) { + Node currentNode = cell.getGraphic(); + Node newNode = (Node) item; + /* + * create a node from the selected node of the listview + * using JFXComboBox {@link #nodeConverterProperty() NodeConverter}) + */ + NodeConverter nc = this.getNodeConverter(); + Node node = nc == null? null : nc.toNode(item); + if (currentNode == null || ! currentNode.equals(newNode)) { + cell.setText(null); + cell.setGraphic(node==null? newNode : node); + } + return node == null; + } else { + // run item through StringConverter if it isn't null + StringConverter c = this.getConverter(); + String s = item == null ? this.getPromptText() : (c == null ? item.toString() : c.toString(item)); + cell.setText(s); + cell.setGraphic(null); + return s == null || s.isEmpty(); + } + } + + /*************************************************************************** + * * + * styleable Properties * + * * + **************************************************************************/ + + /** + * set true to show a float the prompt text when focusing the field + */ + private StyleableBooleanProperty labelFloat = new SimpleStyleableBooleanProperty(StyleableProperties.LABEL_FLOAT, JFXComboBox.this, "lableFloat", false); + + public final StyleableBooleanProperty labelFloatProperty() { + return this.labelFloat; + } + + public final boolean isLabelFloat() { + return this.labelFloatProperty().get(); + } + + public final void setLabelFloat(final boolean labelFloat) { + this.labelFloatProperty().set(labelFloat); + } + + /** + * default color used when the field is unfocused + */ + private StyleableObjectProperty unFocusColor = new SimpleStyleableObjectProperty(StyleableProperties.UNFOCUS_COLOR, JFXComboBox.this, "unFocusColor", Color.rgb(77, 77, 77)); + + public Paint getUnFocusColor() { + return unFocusColor == null ? Color.rgb(77, 77, 77) : unFocusColor.get(); + } + + public StyleableObjectProperty unFocusColorProperty() { + return this.unFocusColor; + } + + public void setUnFocusColor(Paint color) { + this.unFocusColor.set(color); + } + + /** + * default color used when the field is focused + */ + private StyleableObjectProperty focusColor = new SimpleStyleableObjectProperty(StyleableProperties.FOCUS_COLOR, JFXComboBox.this, "focusColor", Color.valueOf("#4059A9")); + + public Paint getFocusColor() { + return focusColor == null ? Color.valueOf("#4059A9") : focusColor.get(); + } + + public StyleableObjectProperty focusColorProperty() { + return this.focusColor; + } + + public void setFocusColor(Paint color) { + this.focusColor.set(color); + } + + + private static class StyleableProperties { + private static final CssMetaData, Paint> UNFOCUS_COLOR = new CssMetaData, Paint>("-jfx-unfocus-color", PaintConverter.getInstance(), Color.valueOf("#A6A6A6")) { + @Override + public boolean isSettable(JFXComboBox control) { + return control.unFocusColor == null || !control.unFocusColor.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXComboBox control) { + return control.unFocusColorProperty(); + } + }; + private static final CssMetaData, Paint> FOCUS_COLOR = new CssMetaData, Paint>("-jfx-focus-color", PaintConverter.getInstance(), Color.valueOf("#3f51b5")) { + @Override + public boolean isSettable(JFXComboBox control) { + return control.focusColor == null || !control.focusColor.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXComboBox control) { + return control.focusColorProperty(); + } + }; + private static final CssMetaData, Boolean> LABEL_FLOAT = new CssMetaData, Boolean>("-jfx-label-float", BooleanConverter.getInstance(), false) { + @Override + public boolean isSettable(JFXComboBox control) { + return control.labelFloat == null || !control.labelFloat.isBound(); + } + + @Override + public StyleableBooleanProperty getStyleableProperty(JFXComboBox control) { + return control.labelFloatProperty(); + } + }; + + + private static final List> CHILD_STYLEABLES; + static { + final List> styleables = new ArrayList>(Control.getClassCssMetaData()); + Collections.addAll(styleables, UNFOCUS_COLOR, FOCUS_COLOR, LABEL_FLOAT); + CHILD_STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + // inherit the styleable properties from parent + private List> STYLEABLES; + + @Override + public List> getControlCssMetaData() { + if (STYLEABLES == null) { + final List> styleables = new ArrayList>(Control.getClassCssMetaData()); + styleables.addAll(getClassCssMetaData()); + styleables.addAll(super.getClassCssMetaData()); + STYLEABLES = Collections.unmodifiableList(styleables); + } + return STYLEABLES; + } + + public static List> getClassCssMetaData() { + return StyleableProperties.CHILD_STYLEABLES; + } +} diff --git a/src/com/jfoenix/controls/JFXDatePicker.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXDatePicker.java similarity index 97% rename from src/com/jfoenix/controls/JFXDatePicker.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXDatePicker.java index 53584d30..24f5e173 100644 --- a/src/com/jfoenix/controls/JFXDatePicker.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/JFXDatePicker.java @@ -1,203 +1,203 @@ -/* - * 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 com.jfoenix.skins.JFXDatePickerSkin; -import com.sun.javafx.css.converters.BooleanConverter; -import com.sun.javafx.css.converters.PaintConverter; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.css.*; -import javafx.geometry.Insets; -import javafx.scene.control.Control; -import javafx.scene.control.DatePicker; -import javafx.scene.control.Skin; -import javafx.scene.layout.Background; -import javafx.scene.layout.BackgroundFill; -import javafx.scene.layout.CornerRadii; -import javafx.scene.layout.StackPane; -import javafx.scene.paint.Color; -import javafx.scene.paint.Paint; - -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * JFXDatePicker is the material design implementation of a date picker. - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXDatePicker extends DatePicker { - - /** - * {@inheritDoc} - */ - public JFXDatePicker() { - super(); - initialize(); - } - - /** - * {@inheritDoc} - */ - public JFXDatePicker(LocalDate localDate) { - super(localDate); - initialize(); - } - - private void initialize() { - this.getStyleClass().add(DEFAULT_STYLE_CLASS); - setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY))); - } - - /** - * {@inheritDoc} - */ - @Override protected Skin createDefaultSkin() { - return new JFXDatePickerSkin(this); - } - - /*************************************************************************** - * * - * Properties * - * * - **************************************************************************/ - - /** - * the parent node used when showing the data picker content as an overlay, - * intead of a popup - */ - private ObjectProperty dialogParent = new SimpleObjectProperty<>(null); - public final ObjectProperty dialogParentProperty() { - return this.dialogParent; - } - public final StackPane getDialogParent() { - return this.dialogParentProperty().get(); - } - public final void setDialogParent(final StackPane dialogParent) { - this.dialogParentProperty().set(dialogParent); - } - - /*************************************************************************** - * * - * Stylesheet Handling * - * * - **************************************************************************/ - - /** - * Initialize the style class to 'jfx-date-picker'. - * - * This is the selector class from which CSS can be used to style - * this control. - */ - private static final String DEFAULT_STYLE_CLASS = "jfx-date-picker"; - - /** - * show the popup as an overlay using JFXDialog - * NOTE: to show it properly the scene root must be StackPane, or the user must set - * the dialog parent manually using the property {{@link #dialogParentProperty()} - */ - private StyleableBooleanProperty overLay = new SimpleStyleableBooleanProperty(StyleableProperties.OVERLAY, JFXDatePicker.this, "overLay", false); - - public final StyleableBooleanProperty overLayProperty() { - return this.overLay; - } - public final boolean isOverLay() { - return overLay == null ? false : this.overLayProperty().get(); - } - public final void setOverLay(final boolean overLay) { - this.overLayProperty().set(overLay); - } - - /** - * the default color used in the data picker content - */ - private StyleableObjectProperty defaultColor = new SimpleStyleableObjectProperty(StyleableProperties.DEFAULT_COLOR, JFXDatePicker.this, "defaultColor", Color.valueOf("#009688")); - - public Paint getDefaultColor(){ - return defaultColor == null ? Color.valueOf("#009688") : defaultColor.get(); - } - public StyleableObjectProperty defaultColorProperty(){ - return this.defaultColor; - } - public void setDefaultColor(Paint color){ - this.defaultColor.set(color); - } - - private static class StyleableProperties { - private static final CssMetaData< JFXDatePicker, Paint> DEFAULT_COLOR = - new CssMetaData< JFXDatePicker, Paint>("-jfx-default-color", - PaintConverter.getInstance(), Color.valueOf("#5A5A5A")) { - @Override - public boolean isSettable(JFXDatePicker control) { - return control.defaultColor == null || !control.defaultColor.isBound(); - } - @Override - public StyleableProperty getStyleableProperty(JFXDatePicker control) { - return control.defaultColorProperty(); - } - }; - - private static final CssMetaData< JFXDatePicker, Boolean> OVERLAY = - new CssMetaData< JFXDatePicker, Boolean>("-jfx-overlay", - BooleanConverter.getInstance(), false) { - @Override - public boolean isSettable(JFXDatePicker control) { - return control.overLay == null || !control.overLay.isBound(); - } - @Override - public StyleableBooleanProperty getStyleableProperty(JFXDatePicker control) { - return control.overLayProperty(); - } - }; - - private static final List> CHILD_STYLEABLES; - static { - final List> styleables = - new ArrayList>(Control.getClassCssMetaData()); - Collections.addAll(styleables, - DEFAULT_COLOR, - OVERLAY); - CHILD_STYLEABLES = Collections.unmodifiableList(styleables); - } - } - - // inherit the styleable properties from parent - private List> STYLEABLES; - - @Override - public List> getControlCssMetaData() { - if(STYLEABLES == null){ - final List> styleables = - new ArrayList>(Control.getClassCssMetaData()); - styleables.addAll(getClassCssMetaData()); - styleables.addAll(super.getClassCssMetaData()); - STYLEABLES = Collections.unmodifiableList(styleables); - } - return STYLEABLES; - } - public static List> getClassCssMetaData() { - return StyleableProperties.CHILD_STYLEABLES; - } - -} +/* + * 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 com.jfoenix.skins.JFXDatePickerSkin; +import com.sun.javafx.css.converters.BooleanConverter; +import com.sun.javafx.css.converters.PaintConverter; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.css.*; +import javafx.geometry.Insets; +import javafx.scene.control.Control; +import javafx.scene.control.DatePicker; +import javafx.scene.control.Skin; +import javafx.scene.layout.Background; +import javafx.scene.layout.BackgroundFill; +import javafx.scene.layout.CornerRadii; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * JFXDatePicker is the material design implementation of a date picker. + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXDatePicker extends DatePicker { + + /** + * {@inheritDoc} + */ + public JFXDatePicker() { + super(); + initialize(); + } + + /** + * {@inheritDoc} + */ + public JFXDatePicker(LocalDate localDate) { + super(localDate); + initialize(); + } + + private void initialize() { + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY))); + } + + /** + * {@inheritDoc} + */ + @Override protected Skin createDefaultSkin() { + return new JFXDatePickerSkin(this); + } + + /*************************************************************************** + * * + * Properties * + * * + **************************************************************************/ + + /** + * the parent node used when showing the data picker content as an overlay, + * intead of a popup + */ + private ObjectProperty dialogParent = new SimpleObjectProperty<>(null); + public final ObjectProperty dialogParentProperty() { + return this.dialogParent; + } + public final StackPane getDialogParent() { + return this.dialogParentProperty().get(); + } + public final void setDialogParent(final StackPane dialogParent) { + this.dialogParentProperty().set(dialogParent); + } + + /*************************************************************************** + * * + * Stylesheet Handling * + * * + **************************************************************************/ + + /** + * Initialize the style class to 'jfx-date-picker'. + * + * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-date-picker"; + + /** + * show the popup as an overlay using JFXDialog + * NOTE: to show it properly the scene root must be StackPane, or the user must set + * the dialog parent manually using the property {{@link #dialogParentProperty()} + */ + private StyleableBooleanProperty overLay = new SimpleStyleableBooleanProperty(StyleableProperties.OVERLAY, JFXDatePicker.this, "overLay", false); + + public final StyleableBooleanProperty overLayProperty() { + return this.overLay; + } + public final boolean isOverLay() { + return overLay == null ? false : this.overLayProperty().get(); + } + public final void setOverLay(final boolean overLay) { + this.overLayProperty().set(overLay); + } + + /** + * the default color used in the data picker content + */ + private StyleableObjectProperty defaultColor = new SimpleStyleableObjectProperty(StyleableProperties.DEFAULT_COLOR, JFXDatePicker.this, "defaultColor", Color.valueOf("#009688")); + + public Paint getDefaultColor(){ + return defaultColor == null ? Color.valueOf("#009688") : defaultColor.get(); + } + public StyleableObjectProperty defaultColorProperty(){ + return this.defaultColor; + } + public void setDefaultColor(Paint color){ + this.defaultColor.set(color); + } + + private static class StyleableProperties { + private static final CssMetaData< JFXDatePicker, Paint> DEFAULT_COLOR = + new CssMetaData< JFXDatePicker, Paint>("-jfx-default-color", + PaintConverter.getInstance(), Color.valueOf("#5A5A5A")) { + @Override + public boolean isSettable(JFXDatePicker control) { + return control.defaultColor == null || !control.defaultColor.isBound(); + } + @Override + public StyleableProperty getStyleableProperty(JFXDatePicker control) { + return control.defaultColorProperty(); + } + }; + + private static final CssMetaData< JFXDatePicker, Boolean> OVERLAY = + new CssMetaData< JFXDatePicker, Boolean>("-jfx-overlay", + BooleanConverter.getInstance(), false) { + @Override + public boolean isSettable(JFXDatePicker control) { + return control.overLay == null || !control.overLay.isBound(); + } + @Override + public StyleableBooleanProperty getStyleableProperty(JFXDatePicker control) { + return control.overLayProperty(); + } + }; + + private static final List> CHILD_STYLEABLES; + static { + final List> styleables = + new ArrayList>(Control.getClassCssMetaData()); + Collections.addAll(styleables, + DEFAULT_COLOR, + OVERLAY); + CHILD_STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + // inherit the styleable properties from parent + private List> STYLEABLES; + + @Override + public List> getControlCssMetaData() { + if(STYLEABLES == null){ + final List> styleables = + new ArrayList>(Control.getClassCssMetaData()); + styleables.addAll(getClassCssMetaData()); + styleables.addAll(super.getClassCssMetaData()); + STYLEABLES = Collections.unmodifiableList(styleables); + } + return STYLEABLES; + } + public static List> getClassCssMetaData() { + return StyleableProperties.CHILD_STYLEABLES; + } + +} diff --git a/src/com/jfoenix/controls/JFXDecorator.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXDecorator.java similarity index 97% rename from src/com/jfoenix/controls/JFXDecorator.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXDecorator.java index d8069532..c1436589 100644 --- a/src/com/jfoenix/controls/JFXDecorator.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/JFXDecorator.java @@ -1,486 +1,486 @@ -/* - * 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 com.jfoenix.svg.SVGGlyph; -import javafx.animation.*; -import javafx.application.Platform; -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleBooleanProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.geometry.*; -import javafx.scene.Cursor; -import javafx.scene.Node; -import javafx.scene.control.Tooltip; -import javafx.scene.input.MouseEvent; -import javafx.scene.layout.*; -import javafx.scene.paint.Color; -import javafx.scene.shape.Rectangle; -import javafx.stage.Screen; -import javafx.stage.Stage; -import javafx.stage.StageStyle; -import javafx.util.Duration; - -import java.util.ArrayList; -import java.util.List; - - -/** - * Window Decorator allow to resize/move its content - * Note: the default close button will call stage.close() which will only close the current stage. - * it will not close the java application, however it can be customized by calling {@link #setOnCloseButtonAction(Runnable)} - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXDecorator extends VBox { - - private Stage primaryStage; - - private double xOffset = 0; - private double yOffset = 0; - private double newX; - private double newY; - private double initX; - private double initY; - - private boolean allowMove = false; - private boolean isDragging = false; - private Timeline windowDecoratorAnimation; - private StackPane contentPlaceHolder = new StackPane(); - private HBox buttonsContainer; - private ObjectProperty onCloseButtonAction = new SimpleObjectProperty<>(()->{primaryStage.close();}); - - private BooleanProperty customMaximize = new SimpleBooleanProperty(false); - private boolean maximized = false; - private BoundingBox originalBox; - private BoundingBox maximizedBox; - - private JFXButton btnMax; - - /** - * Create a window decorator for the specified node with the options: - * - full screen - * - maximize - * - minimize - * - * @param stage the primary stage used by the application - * @param node the node to be decorated - */ - public JFXDecorator(Stage stage, Node node){ - this(stage,node,true,true,true); - } - - /** - * Create a window decorator for the specified node with the options: - * - full screen - * - maximize - * - minimize - * - * @param stage the primary stage used by the application - * @param node the node to be decorated - * @param fullScreen indicates whether to show full screen option or not - * @param max indicates whether to show maximize option or not - * @param min indicates whether to show minimize option or not - */ - public JFXDecorator(Stage stage, Node node, boolean fullScreen, boolean max, boolean min) { - super(); - primaryStage = stage; - /* - * Note that setting the style to TRANSPARENT is causing performance - * degradation, as an alternative we set it to UNDECORATED instead. - */ - primaryStage.initStyle(StageStyle.UNDECORATED); - - setPickOnBounds(false); - this.getStyleClass().add("jfx-decorator"); - - SVGGlyph full = new SVGGlyph(0, "FULLSCREEN", "M598 214h212v212h-84v-128h-128v-84zM726 726v-128h84v212h-212v-84h128zM214 426v-212h212v84h-128v128h-84zM298 598v128h128v84h-212v-212h84z", Color.WHITE); - full.setSize(16, 16); - SVGGlyph minus = new SVGGlyph(0, "MINUS", "M804.571 420.571v109.714q0 22.857-16 38.857t-38.857 16h-694.857q-22.857 0-38.857-16t-16-38.857v-109.714q0-22.857 16-38.857t38.857-16h694.857q22.857 0 38.857 16t16 38.857z", Color.WHITE); - minus.setSize(12, 2); - minus.setTranslateY(4); - SVGGlyph resizeMax = new SVGGlyph(0, "RESIZE_MAX", "M726 810v-596h-428v596h428zM726 44q34 0 59 25t25 59v768q0 34-25 60t-59 26h-428q-34 0-59-26t-25-60v-768q0-34 25-60t59-26z", Color.WHITE); - resizeMax.setSize(12, 12); - SVGGlyph resizeMin = new SVGGlyph(0, "RESIZE_MIN", "M80.842 943.158v-377.264h565.894v377.264h-565.894zM0 404.21v619.79h727.578v-619.79h-727.578zM377.264 161.684h565.894v377.264h-134.736v80.842h215.578v-619.79h-727.578v323.37h80.842v-161.686z", Color.WHITE); - resizeMin.setSize(12, 12); - SVGGlyph close = new SVGGlyph(0, "CLOSE", "M810 274l-238 238 238 238-60 60-238-238-238 238-60-60 238-238-238-238 60-60 238 238 238-238z", Color.WHITE); - close.setSize(12, 12); - - JFXButton btnFull = new JFXButton(); - btnFull.getStyleClass().add("jfx-decorator-button"); - btnFull.setCursor(Cursor.HAND); - btnFull.setOnAction((action)->primaryStage.setFullScreen(!primaryStage.isFullScreen())); - btnFull.setGraphic(full); - btnFull.setTranslateX(-30); - btnFull.setRipplerFill(Color.WHITE); - - JFXButton btnClose = new JFXButton(); - btnClose.getStyleClass().add("jfx-decorator-button"); - btnClose.setCursor(Cursor.HAND); - btnClose.setOnAction((action)->onCloseButtonAction.get().run()); - btnClose.setGraphic(close); - btnClose.setRipplerFill(Color.WHITE); - - JFXButton btnMin = new JFXButton(); - btnMin.getStyleClass().add("jfx-decorator-button"); - btnMin.setCursor(Cursor.HAND); - btnMin.setOnAction((action)->primaryStage.setIconified(true)); - btnMin.setGraphic(minus); - btnMin.setRipplerFill(Color.WHITE); - - btnMax = new JFXButton(); - btnMax.getStyleClass().add("jfx-decorator-button"); - btnMax.setCursor(Cursor.HAND); - btnMax.setRipplerFill(Color.WHITE); - btnMax.setOnAction((action)->{ - if(!isCustomMaximize()){ - primaryStage.setMaximized(!primaryStage.isMaximized()); - maximized = primaryStage.isMaximized(); - if(primaryStage.isMaximized()){ - btnMax.setGraphic(resizeMin); - btnMax.setTooltip(new Tooltip("Restore Down")); - }else{ - btnMax.setGraphic(resizeMax); - btnMax.setTooltip(new Tooltip("Maximize")); - } - }else{ - if (!maximized) { - // store original bounds - originalBox = new BoundingBox(stage.getX(), stage.getY(), stage.getWidth(), stage.getHeight()); - // get the max stage bounds - Screen screen = Screen.getScreensForRectangle(stage.getX(), stage.getY(), stage.getWidth(), stage.getHeight()).get(0); - Rectangle2D bounds = screen.getVisualBounds(); - maximizedBox = new BoundingBox(bounds.getMinX(), bounds.getMinY(), bounds.getWidth(), bounds.getHeight()); - // maximized the stage - stage.setX(maximizedBox.getMinX()); - stage.setY(maximizedBox.getMinY()); - stage.setWidth(maximizedBox.getWidth()); - stage.setHeight(maximizedBox.getHeight()); - btnMax.setGraphic(resizeMin); - btnMax.setTooltip(new Tooltip("Restore Down")); - }else{ - // restore stage to its original size - stage.setX(originalBox.getMinX()); - stage.setY(originalBox.getMinY()); - stage.setWidth(originalBox.getWidth()); - stage.setHeight(originalBox.getHeight()); - originalBox = null; - btnMax.setGraphic(resizeMax); - btnMax.setTooltip(new Tooltip("Maximize")); - } - maximized = !maximized; - } - }); - btnMax.setGraphic(resizeMax); - - - buttonsContainer = new HBox(); - buttonsContainer.getStyleClass().add("jfx-decorator-buttons-container"); - buttonsContainer.setBackground(new Background(new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY))); - // BINDING - // buttonsContainer.backgroundProperty().bind(Bindings.createObjectBinding(()->{ - // return new Background(new BackgroundFill(decoratorColor.get(), CornerRadii.EMPTY, Insets.EMPTY)); - // }, decoratorColor)); - - - buttonsContainer.setPadding(new Insets(4)); - buttonsContainer.setAlignment(Pos.CENTER_RIGHT); - // customize decorator buttons - List btns = new ArrayList<>(); - if(fullScreen) { - btns.add(btnFull); - // maximize/restore the window on header double click - buttonsContainer.setOnMouseClicked((mouseEvent)->{ if(mouseEvent.getClickCount() == 2) btnMax.fire(); }); - } - if(min) btns.add(btnMin); - if(max) btns.add(btnMax); - btns.add(btnClose); - - buttonsContainer.getChildren().addAll(btns); - buttonsContainer.addEventHandler(MouseEvent.MOUSE_ENTERED, (enter)->allowMove = true); - buttonsContainer.addEventHandler(MouseEvent.MOUSE_EXITED, (enter)->{ if(!isDragging) allowMove = false;}); - buttonsContainer.setMinWidth(180); - contentPlaceHolder.getStyleClass().add("jfx-decorator-content-container"); - contentPlaceHolder.setMinSize(0, 0); - contentPlaceHolder.getChildren().add(node); - ((Region)node).setMinSize(0, 0); - VBox.setVgrow(contentPlaceHolder, Priority.ALWAYS); - contentPlaceHolder.getStyleClass().add("resize-border"); - contentPlaceHolder.setBorder(new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(0, 4, 4, 4)))); - // BINDING - // contentPlaceHolder.borderProperty().bind(Bindings.createObjectBinding(()->{ - // return new Border(new BorderStroke(decoratorColor.get(), BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(0, 4, 4, 4))); - // }, decoratorColor)); - - Rectangle clip = new Rectangle(); - clip.widthProperty().bind(((Region)node).widthProperty()); - clip.heightProperty().bind(((Region)node).heightProperty()); - node.setClip(clip); - this.getChildren().addAll(buttonsContainer,contentPlaceHolder); - - primaryStage.fullScreenProperty().addListener((o,oldVal,newVal)->{ - if(newVal){ - // remove border - contentPlaceHolder.getStyleClass().remove("resize-border"); - /* - * note the border property MUST NOT be bound to another property - * when going full screen mode, thus the binding will be lost if exisited - */ - contentPlaceHolder.borderProperty().unbind(); - contentPlaceHolder.setBorder(Border.EMPTY); - if(windowDecoratorAnimation!=null)windowDecoratorAnimation.stop(); - windowDecoratorAnimation = new Timeline(new KeyFrame(Duration.millis(320), new KeyValue(this.translateYProperty(), -buttonsContainer.getHeight(), Interpolator.EASE_BOTH ))); - windowDecoratorAnimation.setOnFinished((finish)->{ - this.getChildren().remove(buttonsContainer); - this.setTranslateY(0); - }); - windowDecoratorAnimation.play(); - }else{ - // add border - if(windowDecoratorAnimation!=null){ - if(windowDecoratorAnimation.getStatus().equals(Animation.Status.RUNNING)) windowDecoratorAnimation.stop(); - else this.getChildren().add(0,buttonsContainer); - } - this.setTranslateY(-buttonsContainer.getHeight()); - windowDecoratorAnimation = new Timeline(new KeyFrame(Duration.millis(320),new KeyValue(this.translateYProperty(), 0, Interpolator.EASE_BOTH))); - windowDecoratorAnimation.setOnFinished((finish)->{ - contentPlaceHolder.setBorder(new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(0, 4, 4, 4)))); - contentPlaceHolder.getStyleClass().add("resize-border"); - // contentPlaceHolder.borderProperty().bind(Bindings.createObjectBinding(()->{ - // return new Border(new BorderStroke(decoratorColor.get(), BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(0, 4, 4, 4))); - // }, decoratorColor)); - }); - windowDecoratorAnimation.play(); - } - }); - - // show the drag cursor on the borders - this.setOnMouseMoved((mouseEvent)->{ - if (primaryStage.isMaximized() || primaryStage.isFullScreen() || maximized) { - this.setCursor(Cursor.DEFAULT); - return; // maximized mode does not support resize - } - if (!primaryStage.isResizable()) { - return; - } - double x = mouseEvent.getX(); - double y = mouseEvent.getY(); - Bounds boundsInParent = this.getBoundsInParent(); - if(contentPlaceHolder.getBorder()!=null && contentPlaceHolder.getBorder().getStrokes().size() > 0){ - double borderWidth = contentPlaceHolder.snappedLeftInset(); - if (isRightEdge(x, y, boundsInParent)) { - if (y < borderWidth) { - this.setCursor(Cursor.NE_RESIZE); - } else if (y > this.getHeight() - (double) (borderWidth)) { - this.setCursor(Cursor.SE_RESIZE); - } else { - this.setCursor( Cursor.E_RESIZE); - } - } else if (isLeftEdge(x, y, boundsInParent)) { - if (y < borderWidth) { - this.setCursor(Cursor.NW_RESIZE); - } else if (y > this.getHeight() - (double) (borderWidth)) { - this.setCursor( Cursor.SW_RESIZE); - } else { - this.setCursor(Cursor.W_RESIZE); - } - } else if (isTopEdge(x, y, boundsInParent)) { - this.setCursor( Cursor.N_RESIZE); - } else if (isBottomEdge(x, y, boundsInParent)) { - this.setCursor(Cursor.S_RESIZE); - } else { - this.setCursor(Cursor.DEFAULT); - } - updateInitMouseValues(mouseEvent); - } - }); - - - // handle drag events on the decorator pane - this.setOnMouseReleased((mouseEvent)-> isDragging = false); - - this.setOnMouseDragged((mouseEvent)->{ - isDragging = true; - if (!mouseEvent.isPrimaryButtonDown() || (xOffset == -1 && yOffset == -1)) { - return; - } - /* - * Long press generates drag event! - */ - if (primaryStage.isFullScreen() || mouseEvent.isStillSincePress() || primaryStage.isMaximized() || maximized) { - return; - } - - newX = mouseEvent.getScreenX(); - newY = mouseEvent.getScreenY(); - - double deltax = newX - initX; - double deltay = newY - initY; - Cursor cursor = this.getCursor(); - - if (Cursor.E_RESIZE.equals(cursor)) { - setStageWidth(primaryStage.getWidth() + deltax); - mouseEvent.consume(); - } else if (Cursor.NE_RESIZE.equals(cursor)) { - if (setStageHeight(primaryStage.getHeight() - deltay)) { - primaryStage.setY(primaryStage.getY() + deltay); - } - setStageWidth(primaryStage.getWidth() + deltax); - mouseEvent.consume(); - } else if (Cursor.SE_RESIZE.equals(cursor)) { - setStageWidth(primaryStage.getWidth() + deltax); - setStageHeight( primaryStage.getHeight() + deltay); - mouseEvent.consume(); - } else if (Cursor.S_RESIZE.equals(cursor)) { - setStageHeight( primaryStage.getHeight() + deltay); - mouseEvent.consume(); - } else if (Cursor.W_RESIZE.equals(cursor)) { - if (setStageWidth( primaryStage.getWidth() - deltax)) { - primaryStage.setX(primaryStage.getX() + deltax); - } - mouseEvent.consume(); - } else if (Cursor.SW_RESIZE.equals(cursor)) { - if (setStageWidth(primaryStage.getWidth() - deltax)) { - primaryStage.setX(primaryStage.getX() + deltax); - } - setStageHeight( primaryStage.getHeight() + deltay); - mouseEvent.consume(); - } else if (Cursor.NW_RESIZE.equals(cursor)) { - if (setStageWidth( primaryStage.getWidth() - deltax)) { - primaryStage.setX(primaryStage.getX() + deltax); - } - if (setStageHeight( primaryStage.getHeight() - deltay)) { - primaryStage.setY(primaryStage.getY() + deltay); - } - mouseEvent.consume(); - } else if (Cursor.N_RESIZE.equals(cursor)) { - if (setStageHeight( primaryStage.getHeight() - deltay)) { - primaryStage.setY(primaryStage.getY() + deltay); - } - mouseEvent.consume(); - }else if(allowMove){ - primaryStage.setX(mouseEvent.getScreenX() - xOffset); - primaryStage.setY(mouseEvent.getScreenY() - yOffset); - mouseEvent.consume(); - } - }); - } - - private void updateInitMouseValues(MouseEvent mouseEvent) { - initX = mouseEvent.getScreenX(); - initY = mouseEvent.getScreenY(); - xOffset = mouseEvent.getSceneX(); - yOffset = mouseEvent.getSceneY(); - } - - - private boolean isRightEdge(double x, double y, Bounds boundsInParent) { - if (x < this.getWidth() && x > this.getWidth() - contentPlaceHolder.snappedLeftInset()) { - return true; - } - return false; - } - private boolean isTopEdge(double x, double y, Bounds boundsInParent) { - if (y >= 0 && y < contentPlaceHolder.snappedLeftInset()) { - return true; - } - return false; - } - private boolean isBottomEdge(double x, double y, Bounds boundsInParent) { - if (y < this.getHeight() && y > this.getHeight() - contentPlaceHolder.snappedLeftInset()) { - return true; - } - return false; - } - private boolean isLeftEdge(double x, double y, Bounds boundsInParent) { - if (x >= 0 && x < contentPlaceHolder.snappedLeftInset()) { - return true; - } - return false; - } - boolean setStageWidth( double width) { - if (width >= primaryStage.getMinWidth() && width >= buttonsContainer.getMinWidth()) { - primaryStage.setWidth(width); - initX = newX; - return true; - }else if( width >= primaryStage.getMinWidth() && width <= buttonsContainer.getMinWidth() ){ - width = buttonsContainer.getMinWidth(); - primaryStage.setWidth(width); - } - return false; - } - boolean setStageHeight(double height) { - if (height >= primaryStage.getMinHeight() && height >= buttonsContainer.getHeight()) { - primaryStage.setHeight(height); - initY = newY; - return true; - }else if(height >= primaryStage.getMinHeight() && height <= buttonsContainer.getHeight()){ - height = buttonsContainer.getHeight(); - primaryStage.setHeight(height); - } - return false; - } - - /** - * set a speficed runnable when clicking on the close button - * @param onCloseButtonAction runnable to be executed - */ - public void setOnCloseButtonAction(Runnable onCloseButtonAction) { - this.onCloseButtonAction.set(onCloseButtonAction); - } - - /** - * this property is used to replace JavaFX maximization - * with a custom one that prevents hiding windows taskbar when - * the JFXDecorator is maximized. - * @return customMaximizeProperty whether to use custom maximization or not. - */ - public final BooleanProperty customMaximizeProperty() { - return this.customMaximize; - } - - /** - * @return whether customMaximizeProperty is active or not - */ - public final boolean isCustomMaximize() { - return this.customMaximizeProperty().get(); - } - - /** - * set customMaximize property - * @param customMaximize - */ - public final void setCustomMaximize(final boolean customMaximize) { - this.customMaximizeProperty().set(customMaximize); - } - - /** - * @param maximized - */ - public void setMaximized(boolean maximized) { - if(this.maximized!=maximized) - Platform.runLater(()->{ - btnMax.fire(); - }); - } - +/* + * 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 com.jfoenix.svg.SVGGlyph; +import javafx.animation.*; +import javafx.application.Platform; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.geometry.*; +import javafx.scene.Cursor; +import javafx.scene.Node; +import javafx.scene.control.Tooltip; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.*; +import javafx.scene.paint.Color; +import javafx.scene.shape.Rectangle; +import javafx.stage.Screen; +import javafx.stage.Stage; +import javafx.stage.StageStyle; +import javafx.util.Duration; + +import java.util.ArrayList; +import java.util.List; + + +/** + * Window Decorator allow to resize/move its content + * Note: the default close button will call stage.close() which will only close the current stage. + * it will not close the java application, however it can be customized by calling {@link #setOnCloseButtonAction(Runnable)} + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXDecorator extends VBox { + + private Stage primaryStage; + + private double xOffset = 0; + private double yOffset = 0; + private double newX; + private double newY; + private double initX; + private double initY; + + private boolean allowMove = false; + private boolean isDragging = false; + private Timeline windowDecoratorAnimation; + private StackPane contentPlaceHolder = new StackPane(); + private HBox buttonsContainer; + private ObjectProperty onCloseButtonAction = new SimpleObjectProperty<>(()->{primaryStage.close();}); + + private BooleanProperty customMaximize = new SimpleBooleanProperty(false); + private boolean maximized = false; + private BoundingBox originalBox; + private BoundingBox maximizedBox; + + private JFXButton btnMax; + + /** + * Create a window decorator for the specified node with the options: + * - full screen + * - maximize + * - minimize + * + * @param stage the primary stage used by the application + * @param node the node to be decorated + */ + public JFXDecorator(Stage stage, Node node){ + this(stage,node,true,true,true); + } + + /** + * Create a window decorator for the specified node with the options: + * - full screen + * - maximize + * - minimize + * + * @param stage the primary stage used by the application + * @param node the node to be decorated + * @param fullScreen indicates whether to show full screen option or not + * @param max indicates whether to show maximize option or not + * @param min indicates whether to show minimize option or not + */ + public JFXDecorator(Stage stage, Node node, boolean fullScreen, boolean max, boolean min) { + super(); + primaryStage = stage; + /* + * Note that setting the style to TRANSPARENT is causing performance + * degradation, as an alternative we set it to UNDECORATED instead. + */ + primaryStage.initStyle(StageStyle.UNDECORATED); + + setPickOnBounds(false); + this.getStyleClass().add("jfx-decorator"); + + SVGGlyph full = new SVGGlyph(0, "FULLSCREEN", "M598 214h212v212h-84v-128h-128v-84zM726 726v-128h84v212h-212v-84h128zM214 426v-212h212v84h-128v128h-84zM298 598v128h128v84h-212v-212h84z", Color.WHITE); + full.setSize(16, 16); + SVGGlyph minus = new SVGGlyph(0, "MINUS", "M804.571 420.571v109.714q0 22.857-16 38.857t-38.857 16h-694.857q-22.857 0-38.857-16t-16-38.857v-109.714q0-22.857 16-38.857t38.857-16h694.857q22.857 0 38.857 16t16 38.857z", Color.WHITE); + minus.setSize(12, 2); + minus.setTranslateY(4); + SVGGlyph resizeMax = new SVGGlyph(0, "RESIZE_MAX", "M726 810v-596h-428v596h428zM726 44q34 0 59 25t25 59v768q0 34-25 60t-59 26h-428q-34 0-59-26t-25-60v-768q0-34 25-60t59-26z", Color.WHITE); + resizeMax.setSize(12, 12); + SVGGlyph resizeMin = new SVGGlyph(0, "RESIZE_MIN", "M80.842 943.158v-377.264h565.894v377.264h-565.894zM0 404.21v619.79h727.578v-619.79h-727.578zM377.264 161.684h565.894v377.264h-134.736v80.842h215.578v-619.79h-727.578v323.37h80.842v-161.686z", Color.WHITE); + resizeMin.setSize(12, 12); + SVGGlyph close = new SVGGlyph(0, "CLOSE", "M810 274l-238 238 238 238-60 60-238-238-238 238-60-60 238-238-238-238 60-60 238 238 238-238z", Color.WHITE); + close.setSize(12, 12); + + JFXButton btnFull = new JFXButton(); + btnFull.getStyleClass().add("jfx-decorator-button"); + btnFull.setCursor(Cursor.HAND); + btnFull.setOnAction((action)->primaryStage.setFullScreen(!primaryStage.isFullScreen())); + btnFull.setGraphic(full); + btnFull.setTranslateX(-30); + btnFull.setRipplerFill(Color.WHITE); + + JFXButton btnClose = new JFXButton(); + btnClose.getStyleClass().add("jfx-decorator-button"); + btnClose.setCursor(Cursor.HAND); + btnClose.setOnAction((action)->onCloseButtonAction.get().run()); + btnClose.setGraphic(close); + btnClose.setRipplerFill(Color.WHITE); + + JFXButton btnMin = new JFXButton(); + btnMin.getStyleClass().add("jfx-decorator-button"); + btnMin.setCursor(Cursor.HAND); + btnMin.setOnAction((action)->primaryStage.setIconified(true)); + btnMin.setGraphic(minus); + btnMin.setRipplerFill(Color.WHITE); + + btnMax = new JFXButton(); + btnMax.getStyleClass().add("jfx-decorator-button"); + btnMax.setCursor(Cursor.HAND); + btnMax.setRipplerFill(Color.WHITE); + btnMax.setOnAction((action)->{ + if(!isCustomMaximize()){ + primaryStage.setMaximized(!primaryStage.isMaximized()); + maximized = primaryStage.isMaximized(); + if(primaryStage.isMaximized()){ + btnMax.setGraphic(resizeMin); + btnMax.setTooltip(new Tooltip("Restore Down")); + }else{ + btnMax.setGraphic(resizeMax); + btnMax.setTooltip(new Tooltip("Maximize")); + } + }else{ + if (!maximized) { + // store original bounds + originalBox = new BoundingBox(stage.getX(), stage.getY(), stage.getWidth(), stage.getHeight()); + // get the max stage bounds + Screen screen = Screen.getScreensForRectangle(stage.getX(), stage.getY(), stage.getWidth(), stage.getHeight()).get(0); + Rectangle2D bounds = screen.getVisualBounds(); + maximizedBox = new BoundingBox(bounds.getMinX(), bounds.getMinY(), bounds.getWidth(), bounds.getHeight()); + // maximized the stage + stage.setX(maximizedBox.getMinX()); + stage.setY(maximizedBox.getMinY()); + stage.setWidth(maximizedBox.getWidth()); + stage.setHeight(maximizedBox.getHeight()); + btnMax.setGraphic(resizeMin); + btnMax.setTooltip(new Tooltip("Restore Down")); + }else{ + // restore stage to its original size + stage.setX(originalBox.getMinX()); + stage.setY(originalBox.getMinY()); + stage.setWidth(originalBox.getWidth()); + stage.setHeight(originalBox.getHeight()); + originalBox = null; + btnMax.setGraphic(resizeMax); + btnMax.setTooltip(new Tooltip("Maximize")); + } + maximized = !maximized; + } + }); + btnMax.setGraphic(resizeMax); + + + buttonsContainer = new HBox(); + buttonsContainer.getStyleClass().add("jfx-decorator-buttons-container"); + buttonsContainer.setBackground(new Background(new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY))); + // BINDING + // buttonsContainer.backgroundProperty().bind(Bindings.createObjectBinding(()->{ + // return new Background(new BackgroundFill(decoratorColor.get(), CornerRadii.EMPTY, Insets.EMPTY)); + // }, decoratorColor)); + + + buttonsContainer.setPadding(new Insets(4)); + buttonsContainer.setAlignment(Pos.CENTER_RIGHT); + // customize decorator buttons + List btns = new ArrayList<>(); + if(fullScreen) { + btns.add(btnFull); + // maximize/restore the window on header double click + buttonsContainer.setOnMouseClicked((mouseEvent)->{ if(mouseEvent.getClickCount() == 2) btnMax.fire(); }); + } + if(min) btns.add(btnMin); + if(max) btns.add(btnMax); + btns.add(btnClose); + + buttonsContainer.getChildren().addAll(btns); + buttonsContainer.addEventHandler(MouseEvent.MOUSE_ENTERED, (enter)->allowMove = true); + buttonsContainer.addEventHandler(MouseEvent.MOUSE_EXITED, (enter)->{ if(!isDragging) allowMove = false;}); + buttonsContainer.setMinWidth(180); + contentPlaceHolder.getStyleClass().add("jfx-decorator-content-container"); + contentPlaceHolder.setMinSize(0, 0); + contentPlaceHolder.getChildren().add(node); + ((Region)node).setMinSize(0, 0); + VBox.setVgrow(contentPlaceHolder, Priority.ALWAYS); + contentPlaceHolder.getStyleClass().add("resize-border"); + contentPlaceHolder.setBorder(new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(0, 4, 4, 4)))); + // BINDING + // contentPlaceHolder.borderProperty().bind(Bindings.createObjectBinding(()->{ + // return new Border(new BorderStroke(decoratorColor.get(), BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(0, 4, 4, 4))); + // }, decoratorColor)); + + Rectangle clip = new Rectangle(); + clip.widthProperty().bind(((Region)node).widthProperty()); + clip.heightProperty().bind(((Region)node).heightProperty()); + node.setClip(clip); + this.getChildren().addAll(buttonsContainer,contentPlaceHolder); + + primaryStage.fullScreenProperty().addListener((o,oldVal,newVal)->{ + if(newVal){ + // remove border + contentPlaceHolder.getStyleClass().remove("resize-border"); + /* + * note the border property MUST NOT be bound to another property + * when going full screen mode, thus the binding will be lost if exisited + */ + contentPlaceHolder.borderProperty().unbind(); + contentPlaceHolder.setBorder(Border.EMPTY); + if(windowDecoratorAnimation!=null)windowDecoratorAnimation.stop(); + windowDecoratorAnimation = new Timeline(new KeyFrame(Duration.millis(320), new KeyValue(this.translateYProperty(), -buttonsContainer.getHeight(), Interpolator.EASE_BOTH ))); + windowDecoratorAnimation.setOnFinished((finish)->{ + this.getChildren().remove(buttonsContainer); + this.setTranslateY(0); + }); + windowDecoratorAnimation.play(); + }else{ + // add border + if(windowDecoratorAnimation!=null){ + if(windowDecoratorAnimation.getStatus().equals(Animation.Status.RUNNING)) windowDecoratorAnimation.stop(); + else this.getChildren().add(0,buttonsContainer); + } + this.setTranslateY(-buttonsContainer.getHeight()); + windowDecoratorAnimation = new Timeline(new KeyFrame(Duration.millis(320),new KeyValue(this.translateYProperty(), 0, Interpolator.EASE_BOTH))); + windowDecoratorAnimation.setOnFinished((finish)->{ + contentPlaceHolder.setBorder(new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(0, 4, 4, 4)))); + contentPlaceHolder.getStyleClass().add("resize-border"); + // contentPlaceHolder.borderProperty().bind(Bindings.createObjectBinding(()->{ + // return new Border(new BorderStroke(decoratorColor.get(), BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(0, 4, 4, 4))); + // }, decoratorColor)); + }); + windowDecoratorAnimation.play(); + } + }); + + // show the drag cursor on the borders + this.setOnMouseMoved((mouseEvent)->{ + if (primaryStage.isMaximized() || primaryStage.isFullScreen() || maximized) { + this.setCursor(Cursor.DEFAULT); + return; // maximized mode does not support resize + } + if (!primaryStage.isResizable()) { + return; + } + double x = mouseEvent.getX(); + double y = mouseEvent.getY(); + Bounds boundsInParent = this.getBoundsInParent(); + if(contentPlaceHolder.getBorder()!=null && contentPlaceHolder.getBorder().getStrokes().size() > 0){ + double borderWidth = contentPlaceHolder.snappedLeftInset(); + if (isRightEdge(x, y, boundsInParent)) { + if (y < borderWidth) { + this.setCursor(Cursor.NE_RESIZE); + } else if (y > this.getHeight() - (double) (borderWidth)) { + this.setCursor(Cursor.SE_RESIZE); + } else { + this.setCursor( Cursor.E_RESIZE); + } + } else if (isLeftEdge(x, y, boundsInParent)) { + if (y < borderWidth) { + this.setCursor(Cursor.NW_RESIZE); + } else if (y > this.getHeight() - (double) (borderWidth)) { + this.setCursor( Cursor.SW_RESIZE); + } else { + this.setCursor(Cursor.W_RESIZE); + } + } else if (isTopEdge(x, y, boundsInParent)) { + this.setCursor( Cursor.N_RESIZE); + } else if (isBottomEdge(x, y, boundsInParent)) { + this.setCursor(Cursor.S_RESIZE); + } else { + this.setCursor(Cursor.DEFAULT); + } + updateInitMouseValues(mouseEvent); + } + }); + + + // handle drag events on the decorator pane + this.setOnMouseReleased((mouseEvent)-> isDragging = false); + + this.setOnMouseDragged((mouseEvent)->{ + isDragging = true; + if (!mouseEvent.isPrimaryButtonDown() || (xOffset == -1 && yOffset == -1)) { + return; + } + /* + * Long press generates drag event! + */ + if (primaryStage.isFullScreen() || mouseEvent.isStillSincePress() || primaryStage.isMaximized() || maximized) { + return; + } + + newX = mouseEvent.getScreenX(); + newY = mouseEvent.getScreenY(); + + double deltax = newX - initX; + double deltay = newY - initY; + Cursor cursor = this.getCursor(); + + if (Cursor.E_RESIZE.equals(cursor)) { + setStageWidth(primaryStage.getWidth() + deltax); + mouseEvent.consume(); + } else if (Cursor.NE_RESIZE.equals(cursor)) { + if (setStageHeight(primaryStage.getHeight() - deltay)) { + primaryStage.setY(primaryStage.getY() + deltay); + } + setStageWidth(primaryStage.getWidth() + deltax); + mouseEvent.consume(); + } else if (Cursor.SE_RESIZE.equals(cursor)) { + setStageWidth(primaryStage.getWidth() + deltax); + setStageHeight( primaryStage.getHeight() + deltay); + mouseEvent.consume(); + } else if (Cursor.S_RESIZE.equals(cursor)) { + setStageHeight( primaryStage.getHeight() + deltay); + mouseEvent.consume(); + } else if (Cursor.W_RESIZE.equals(cursor)) { + if (setStageWidth( primaryStage.getWidth() - deltax)) { + primaryStage.setX(primaryStage.getX() + deltax); + } + mouseEvent.consume(); + } else if (Cursor.SW_RESIZE.equals(cursor)) { + if (setStageWidth(primaryStage.getWidth() - deltax)) { + primaryStage.setX(primaryStage.getX() + deltax); + } + setStageHeight( primaryStage.getHeight() + deltay); + mouseEvent.consume(); + } else if (Cursor.NW_RESIZE.equals(cursor)) { + if (setStageWidth( primaryStage.getWidth() - deltax)) { + primaryStage.setX(primaryStage.getX() + deltax); + } + if (setStageHeight( primaryStage.getHeight() - deltay)) { + primaryStage.setY(primaryStage.getY() + deltay); + } + mouseEvent.consume(); + } else if (Cursor.N_RESIZE.equals(cursor)) { + if (setStageHeight( primaryStage.getHeight() - deltay)) { + primaryStage.setY(primaryStage.getY() + deltay); + } + mouseEvent.consume(); + }else if(allowMove){ + primaryStage.setX(mouseEvent.getScreenX() - xOffset); + primaryStage.setY(mouseEvent.getScreenY() - yOffset); + mouseEvent.consume(); + } + }); + } + + private void updateInitMouseValues(MouseEvent mouseEvent) { + initX = mouseEvent.getScreenX(); + initY = mouseEvent.getScreenY(); + xOffset = mouseEvent.getSceneX(); + yOffset = mouseEvent.getSceneY(); + } + + + private boolean isRightEdge(double x, double y, Bounds boundsInParent) { + if (x < this.getWidth() && x > this.getWidth() - contentPlaceHolder.snappedLeftInset()) { + return true; + } + return false; + } + private boolean isTopEdge(double x, double y, Bounds boundsInParent) { + if (y >= 0 && y < contentPlaceHolder.snappedLeftInset()) { + return true; + } + return false; + } + private boolean isBottomEdge(double x, double y, Bounds boundsInParent) { + if (y < this.getHeight() && y > this.getHeight() - contentPlaceHolder.snappedLeftInset()) { + return true; + } + return false; + } + private boolean isLeftEdge(double x, double y, Bounds boundsInParent) { + if (x >= 0 && x < contentPlaceHolder.snappedLeftInset()) { + return true; + } + return false; + } + boolean setStageWidth( double width) { + if (width >= primaryStage.getMinWidth() && width >= buttonsContainer.getMinWidth()) { + primaryStage.setWidth(width); + initX = newX; + return true; + }else if( width >= primaryStage.getMinWidth() && width <= buttonsContainer.getMinWidth() ){ + width = buttonsContainer.getMinWidth(); + primaryStage.setWidth(width); + } + return false; + } + boolean setStageHeight(double height) { + if (height >= primaryStage.getMinHeight() && height >= buttonsContainer.getHeight()) { + primaryStage.setHeight(height); + initY = newY; + return true; + }else if(height >= primaryStage.getMinHeight() && height <= buttonsContainer.getHeight()){ + height = buttonsContainer.getHeight(); + primaryStage.setHeight(height); + } + return false; + } + + /** + * set a speficed runnable when clicking on the close button + * @param onCloseButtonAction runnable to be executed + */ + public void setOnCloseButtonAction(Runnable onCloseButtonAction) { + this.onCloseButtonAction.set(onCloseButtonAction); + } + + /** + * this property is used to replace JavaFX maximization + * with a custom one that prevents hiding windows taskbar when + * the JFXDecorator is maximized. + * @return customMaximizeProperty whether to use custom maximization or not. + */ + public final BooleanProperty customMaximizeProperty() { + return this.customMaximize; + } + + /** + * @return whether customMaximizeProperty is active or not + */ + public final boolean isCustomMaximize() { + return this.customMaximizeProperty().get(); + } + + /** + * set customMaximize property + * @param customMaximize + */ + public final void setCustomMaximize(final boolean customMaximize) { + this.customMaximizeProperty().set(customMaximize); + } + + /** + * @param maximized + */ + public void setMaximized(boolean maximized) { + if(this.maximized!=maximized) + Platform.runLater(()->{ + btnMax.fire(); + }); + } + } \ No newline at end of file diff --git a/src/com/jfoenix/controls/JFXDialog.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXDialog.java similarity index 97% rename from src/com/jfoenix/controls/JFXDialog.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXDialog.java index bd261173..4195eff9 100644 --- a/src/com/jfoenix/controls/JFXDialog.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/JFXDialog.java @@ -1,534 +1,534 @@ -/* - * 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 com.jfoenix.controls.events.JFXDialogEvent; -import com.jfoenix.converters.DialogTransitionConverter; -import com.jfoenix.effects.JFXDepthManager; -import com.jfoenix.transitions.CachedTransition; -import javafx.animation.*; -import javafx.beans.DefaultProperty; -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleBooleanProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.css.*; -import javafx.event.EventHandler; -import javafx.geometry.Pos; -import javafx.scene.Parent; -import javafx.scene.input.MouseEvent; -import javafx.scene.layout.*; -import javafx.scene.paint.Color; -import javafx.util.Duration; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Note: for JFXDialog to work properly, the root node MUST - * be of type {@link StackPane} - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -@DefaultProperty(value="content") -public class JFXDialog extends StackPane { - - // public static enum JFXDialogLayout{PLAIN, HEADING, ACTIONS, BACKDROP}; - public static enum DialogTransition{CENTER, TOP, RIGHT, BOTTOM, LEFT}; - - private StackPane contentHolder; - - private double offsetX = 0; - private double offsetY = 0; - - private StackPane dialogContainer; - private Region content; - private Transition animation; - - EventHandler closeHandler = (e)->close(); - - /** - * creates empty JFXDialog control with CENTER animation type - */ - public JFXDialog(){ - this(null,null,DialogTransition.CENTER); - } - - /** - * creates JFXDialog control with a specified animation type, the animation type - * can be one of the following: - *

- * - * @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 transitionType = new SimpleStyleableObjectProperty(StyleableProperties.DIALOG_TRANSITION, JFXDialog.this, "dialogTransition", DialogTransition.CENTER ); - - public DialogTransition getTransitionType(){ - return transitionType == null ? DialogTransition.CENTER : transitionType.get(); - } - public StyleableObjectProperty transitionTypeProperty(){ - return this.transitionType; - } - public void setTransitionType(DialogTransition transition){ - this.transitionType.set(transition); - } - - private static class StyleableProperties { - private static final CssMetaData< JFXDialog, DialogTransition> DIALOG_TRANSITION = - new CssMetaData< JFXDialog, DialogTransition>("-jfx-dialog-transition", - DialogTransitionConverter.getInstance(), DialogTransition.CENTER) { - @Override - public boolean isSettable(JFXDialog control) { - return control.transitionType == null || !control.transitionType.isBound(); - } - @Override - public StyleableProperty getStyleableProperty(JFXDialog control) { - return control.transitionTypeProperty(); - } - }; - - private static final List> CHILD_STYLEABLES; - static { - final List> styleables = - new ArrayList>(Parent.getClassCssMetaData()); - Collections.addAll(styleables, - DIALOG_TRANSITION - ); - CHILD_STYLEABLES = Collections.unmodifiableList(styleables); - } - } - - // inherit the styleable properties from parent - private List> STYLEABLES; - - @Override - public List> getCssMetaData() { - if(STYLEABLES == null){ - final List> styleables = - new ArrayList>(Parent.getClassCssMetaData()); - styleables.addAll(getClassCssMetaData()); - styleables.addAll(super.getClassCssMetaData()); - STYLEABLES = Collections.unmodifiableList(styleables); - } - return STYLEABLES; - } - public static List> getClassCssMetaData() { - return StyleableProperties.CHILD_STYLEABLES; - } - - - - /*************************************************************************** - * * - * Custom Events * - * * - **************************************************************************/ - - private ObjectProperty> onDialogClosedProperty = new SimpleObjectProperty<>((closed)->{}); - - /** - * Defines a function to be called when the dialog is closed. - * Note: it will be triggered after the close animation is finished. - */ - public void setOnDialogClosed(EventHandler handler){ - onDialogClosedProperty.set(handler); - } - - public EventHandler getOnDialogClosed() { - return onDialogClosedProperty.get(); - } - - - private ObjectProperty> onDialogOpenedProperty = new SimpleObjectProperty<>((opened)->{}); - - /** - * Defines a function to be called when the dialog is opened. - * Note: it will be triggered after the show animation is finished. - */ - public void setOnDialogOpened(EventHandler handler){ - onDialogOpenedProperty.set(handler); - } - - public EventHandler getOnDialogOpened() { - return onDialogOpenedProperty.get(); - } -} +/* + * 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 com.jfoenix.controls.events.JFXDialogEvent; +import com.jfoenix.converters.DialogTransitionConverter; +import com.jfoenix.effects.JFXDepthManager; +import com.jfoenix.transitions.CachedTransition; +import javafx.animation.*; +import javafx.beans.DefaultProperty; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.css.*; +import javafx.event.EventHandler; +import javafx.geometry.Pos; +import javafx.scene.Parent; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.*; +import javafx.scene.paint.Color; +import javafx.util.Duration; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Note: for JFXDialog to work properly, the root node MUST + * be of type {@link StackPane} + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +@DefaultProperty(value="content") +public class JFXDialog extends StackPane { + + // public static enum JFXDialogLayout{PLAIN, HEADING, ACTIONS, BACKDROP}; + public static enum DialogTransition{CENTER, TOP, RIGHT, BOTTOM, LEFT}; + + private StackPane contentHolder; + + private double offsetX = 0; + private double offsetY = 0; + + private StackPane dialogContainer; + private Region content; + private Transition animation; + + EventHandler closeHandler = (e)->close(); + + /** + * creates empty JFXDialog control with CENTER animation type + */ + public JFXDialog(){ + this(null,null,DialogTransition.CENTER); + } + + /** + * creates JFXDialog control with a specified animation type, the animation type + * can be one of the following: + *
    + *
  • CENTER
  • + *
  • TOP
  • + *
  • RIGHT
  • + *
  • BOTTOM
  • + *
  • LEFT
  • + *
+ * + * @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: + *
    + *
  • CENTER
  • + *
  • TOP
  • + *
  • RIGHT
  • + *
  • BOTTOM
  • + *
  • LEFT
  • + *
+ * + * @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: + *
    + *
  • CENTER
  • + *
  • TOP
  • + *
  • RIGHT
  • + *
  • BOTTOM
  • + *
  • LEFT
  • + *
+ */ + private StyleableObjectProperty transitionType = new SimpleStyleableObjectProperty(StyleableProperties.DIALOG_TRANSITION, JFXDialog.this, "dialogTransition", DialogTransition.CENTER ); + + public DialogTransition getTransitionType(){ + return transitionType == null ? DialogTransition.CENTER : transitionType.get(); + } + public StyleableObjectProperty transitionTypeProperty(){ + return this.transitionType; + } + public void setTransitionType(DialogTransition transition){ + this.transitionType.set(transition); + } + + private static class StyleableProperties { + private static final CssMetaData< JFXDialog, DialogTransition> DIALOG_TRANSITION = + new CssMetaData< JFXDialog, DialogTransition>("-jfx-dialog-transition", + DialogTransitionConverter.getInstance(), DialogTransition.CENTER) { + @Override + public boolean isSettable(JFXDialog control) { + return control.transitionType == null || !control.transitionType.isBound(); + } + @Override + public StyleableProperty getStyleableProperty(JFXDialog control) { + return control.transitionTypeProperty(); + } + }; + + private static final List> CHILD_STYLEABLES; + static { + final List> styleables = + new ArrayList>(Parent.getClassCssMetaData()); + Collections.addAll(styleables, + DIALOG_TRANSITION + ); + CHILD_STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + // inherit the styleable properties from parent + private List> STYLEABLES; + + @Override + public List> getCssMetaData() { + if(STYLEABLES == null){ + final List> styleables = + new ArrayList>(Parent.getClassCssMetaData()); + styleables.addAll(getClassCssMetaData()); + styleables.addAll(super.getClassCssMetaData()); + STYLEABLES = Collections.unmodifiableList(styleables); + } + return STYLEABLES; + } + public static List> getClassCssMetaData() { + return StyleableProperties.CHILD_STYLEABLES; + } + + + + /*************************************************************************** + * * + * Custom Events * + * * + **************************************************************************/ + + private ObjectProperty> onDialogClosedProperty = new SimpleObjectProperty<>((closed)->{}); + + /** + * Defines a function to be called when the dialog is closed. + * Note: it will be triggered after the close animation is finished. + */ + public void setOnDialogClosed(EventHandler handler){ + onDialogClosedProperty.set(handler); + } + + public EventHandler getOnDialogClosed() { + return onDialogClosedProperty.get(); + } + + + private ObjectProperty> onDialogOpenedProperty = new SimpleObjectProperty<>((opened)->{}); + + /** + * Defines a function to be called when the dialog is opened. + * Note: it will be triggered after the show animation is finished. + */ + public void setOnDialogOpened(EventHandler handler){ + onDialogOpenedProperty.set(handler); + } + + public EventHandler getOnDialogOpened() { + return onDialogOpenedProperty.get(); + } +} diff --git a/src/com/jfoenix/controls/JFXDialogLayout.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXDialogLayout.java similarity index 96% rename from src/com/jfoenix/controls/JFXDialogLayout.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXDialogLayout.java index 2f42646a..88b9c409 100644 --- a/src/com/jfoenix/controls/JFXDialogLayout.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/JFXDialogLayout.java @@ -1,133 +1,133 @@ -/* - * 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.collections.ObservableList; -import javafx.geometry.Insets; -import javafx.scene.Node; -import javafx.scene.layout.FlowPane; -import javafx.scene.layout.StackPane; -import javafx.scene.layout.VBox; - -import java.util.List; - -/** - * Default dialog layout according to material design guidelines. - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXDialogLayout extends StackPane { - - private VBox layout = new VBox(); - private StackPane heading = new StackPane(); - private StackPane body = new StackPane(); - private FlowPane actions = new FlowPane(); - - /** - * creates empty dialog layout - */ - public JFXDialogLayout() { - super(); - initialize(); - layout.getChildren().add(heading); - heading.getStyleClass().add("jfx-layout-heading"); - heading.getStyleClass().add("title"); - layout.getChildren().add(body); - body.getStyleClass().add("jfx-layout-body"); - body.prefHeightProperty().bind(this.prefHeightProperty()); - body.prefWidthProperty().bind(this.prefWidthProperty()); - layout.getChildren().add(actions); - actions.getStyleClass().add("jfx-layout-actions"); - this.getChildren().add(layout); - } - - /*************************************************************************** - * * - * Setters / Getters * - * * - **************************************************************************/ - - public ObservableList getHeading() { - return heading.getChildren(); - } - - /** - * set header node - * @param titleContent - */ - public void setHeading(Node... titleContent) { - this.heading.getChildren().setAll(titleContent); - } - - public ObservableList getBody() { - return body.getChildren(); - } - - /** - * set body node - * @param body - */ - public void setBody(Node... body) { - this.body.getChildren().setAll(body); - } - - public ObservableList getActions() { - return actions.getChildren(); - } - - /** - * set actions of the dialog (Accept, Cancel,...) - * - * @param actions - */ - public void setActions(Node... actions) { - this.actions.getChildren().setAll(actions); - } - - public void setActions(List actions) { - this.actions.getChildren().setAll(actions); - } - - /*************************************************************************** - * * - * Stylesheet Handling * - * * - **************************************************************************/ - /** - * Initialize the style class to 'jfx-dialog-layout'. - * - * This is the selector class from which CSS can be used to style - * this control. - */ - private static final String DEFAULT_STYLE_CLASS = "jfx-dialog-layout"; - - - private void initialize() { - this.getStyleClass().add(DEFAULT_STYLE_CLASS); - this.setPadding(new Insets(24,24,16,24)); - this.setStyle("-fx-text-fill: rgba(0, 0, 0, 0.87);"); - heading.setStyle("-fx-font-weight: BOLD;-fx-alignment: center-left;"); - heading.setPadding(new Insets(5,0,5,0)); - body.setStyle("-fx-pref-width: 400px;-fx-wrap-text: true;"); - actions.setStyle("-fx-alignment: center-right ;"); - actions.setPadding(new Insets(10,0,0,0)); - } -} +/* + * 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.collections.ObservableList; +import javafx.geometry.Insets; +import javafx.scene.Node; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; + +import java.util.List; + +/** + * Default dialog layout according to material design guidelines. + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXDialogLayout extends StackPane { + + private VBox layout = new VBox(); + private StackPane heading = new StackPane(); + private StackPane body = new StackPane(); + private FlowPane actions = new FlowPane(); + + /** + * creates empty dialog layout + */ + public JFXDialogLayout() { + super(); + initialize(); + layout.getChildren().add(heading); + heading.getStyleClass().add("jfx-layout-heading"); + heading.getStyleClass().add("title"); + layout.getChildren().add(body); + body.getStyleClass().add("jfx-layout-body"); + body.prefHeightProperty().bind(this.prefHeightProperty()); + body.prefWidthProperty().bind(this.prefWidthProperty()); + layout.getChildren().add(actions); + actions.getStyleClass().add("jfx-layout-actions"); + this.getChildren().add(layout); + } + + /*************************************************************************** + * * + * Setters / Getters * + * * + **************************************************************************/ + + public ObservableList getHeading() { + return heading.getChildren(); + } + + /** + * set header node + * @param titleContent + */ + public void setHeading(Node... titleContent) { + this.heading.getChildren().setAll(titleContent); + } + + public ObservableList getBody() { + return body.getChildren(); + } + + /** + * set body node + * @param body + */ + public void setBody(Node... body) { + this.body.getChildren().setAll(body); + } + + public ObservableList getActions() { + return actions.getChildren(); + } + + /** + * set actions of the dialog (Accept, Cancel,...) + * + * @param actions + */ + public void setActions(Node... actions) { + this.actions.getChildren().setAll(actions); + } + + public void setActions(List actions) { + this.actions.getChildren().setAll(actions); + } + + /*************************************************************************** + * * + * Stylesheet Handling * + * * + **************************************************************************/ + /** + * Initialize the style class to 'jfx-dialog-layout'. + * + * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-dialog-layout"; + + + private void initialize() { + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + this.setPadding(new Insets(24,24,16,24)); + this.setStyle("-fx-text-fill: rgba(0, 0, 0, 0.87);"); + heading.setStyle("-fx-font-weight: BOLD;-fx-alignment: center-left;"); + heading.setPadding(new Insets(5,0,5,0)); + body.setStyle("-fx-pref-width: 400px;-fx-wrap-text: true;"); + actions.setStyle("-fx-alignment: center-right ;"); + actions.setPadding(new Insets(10,0,0,0)); + } +} diff --git a/src/com/jfoenix/controls/JFXDrawer.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXDrawer.java similarity index 97% rename from src/com/jfoenix/controls/JFXDrawer.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXDrawer.java index feae6b9a..e09237f2 100644 --- a/src/com/jfoenix/controls/JFXDrawer.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/JFXDrawer.java @@ -1,788 +1,788 @@ -/* - * 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 com.jfoenix.controls.events.JFXDrawerEvent; -import com.jfoenix.transitions.CachedTransition; -import javafx.animation.Animation.Status; -import javafx.animation.*; -import javafx.beans.binding.Bindings; -import javafx.beans.property.*; -import javafx.beans.value.ChangeListener; -import javafx.collections.ObservableList; -import javafx.event.ActionEvent; -import javafx.event.Event; -import javafx.event.EventHandler; -import javafx.geometry.Bounds; -import javafx.geometry.Insets; -import javafx.geometry.Pos; -import javafx.scene.Cursor; -import javafx.scene.Node; -import javafx.scene.Scene; -import javafx.scene.input.MouseEvent; -import javafx.scene.layout.*; -import javafx.scene.paint.Color; -import javafx.util.Callback; -import javafx.util.Duration; - -import java.util.ArrayList; - -/** - * JFXDrawer is material design implementation of drawer. - * the drawer has two main nodes, the content and side pane. - *
    - *
  • content pane: is a stack pane that holds the nodes inside the drawer
  • - *
  • side pane: is a stack pane that holds the nodes inside the drawer side area (Drawable node)
  • - *
- * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXDrawer extends StackPane { - - public static enum DrawerDirection{ - LEFT(1), RIGHT(-1), TOP(1), BOTTOM(-1); - private double numVal; - DrawerDirection(double numVal) { - this.numVal = numVal; - } - public double doubleValue() { - return numVal; - } - }; - - private StackPane overlayPane = new StackPane(); - StackPane sidePane = new StackPane(); - private StackPane content = new StackPane(); - private Transition drawerTransition; - // private Transition outTransition; - private Transition partialTransition; - private Duration holdTime = Duration.seconds(0.2); - private PauseTransition holdTimer = new PauseTransition(holdTime); - - private double initOffset = 30; - private DoubleProperty initTranslate = new SimpleDoubleProperty(); - private BooleanProperty overLayVisible = new SimpleBooleanProperty(true); - private double activeOffset = 20; - private double startMouse = -1; - private double startTranslate = -1; - private double startSize = -1; - private DoubleProperty translateProperty = sidePane.translateXProperty(); - private boolean resizable = false; - private boolean openCalled = false; - private boolean closeCalled = true; - - private DoubleProperty defaultSizeProperty = new SimpleDoubleProperty(); - private ObjectProperty maxSizeProperty = new SimpleObjectProperty<>(sidePane.maxWidthProperty()); - private ObjectProperty minSizeProperty = new SimpleObjectProperty<>(sidePane.minWidthProperty()); - private ObjectProperty prefSizeProperty = new SimpleObjectProperty<>(sidePane.prefWidthProperty()); - private ObjectProperty sizeProperty = new SimpleObjectProperty<>(sidePane.widthProperty()); - private ObjectProperty parentSizeProperty = new SimpleObjectProperty<>(); - private ObjectProperty boundedNode = new SimpleObjectProperty<>(); - - private SimpleObjectProperty directionProperty = new SimpleObjectProperty(DrawerDirection.LEFT); - - /** - * creates empy drawer node - */ - public JFXDrawer(){ - super(); - initialize(); - - overlayPane.setBackground(new Background(new BackgroundFill(Color.rgb(0, 0, 0, 0.1), CornerRadii.EMPTY, Insets.EMPTY))); - overlayPane.getStyleClass().add("jfx-drawer-overlay-pane"); - overlayPane.setOpacity(0); - - sidePane.getStyleClass().add("jfx-drawer-side-pane"); - sidePane.setBackground(new Background(new BackgroundFill(Color.rgb(255, 255, 255, 1), CornerRadii.EMPTY, Insets.EMPTY))); - sidePane.setPickOnBounds(false); - // causes performance issue when animating the drawer -// JFXDepthManager.setDepth(sidePane, 2); - - this.getChildren().add(content); - - // add listeners - overlayPane.setOnMouseClicked((e) -> close()); - initListeners(); - - // init size value - setDefaultDrawerSize(100); - } - - private void initialize() { - this.getStyleClass().add(DEFAULT_STYLE_CLASS); - } - - private void initListeners(){ - updateDirection(directionProperty.get()); - initTranslate.bind(Bindings.createDoubleBinding(()-> -1 * directionProperty.get().doubleValue() * defaultSizeProperty.getValue() - initOffset * directionProperty.get().doubleValue(), defaultSizeProperty, directionProperty )); - - // add listeners to update drawer properties - overLayVisibleProperty().addListener((o,oldVal,newVal)->{ - overlayPane.setStyle(!newVal?"-fx-background-color : transparent;":""); - overlayPane.setMouseTransparent(!newVal); - overlayPane.setPickOnBounds(newVal); - }); - - directionProperty.addListener((o,oldVal,newVal)-> updateDirection(newVal)); - initTranslate.addListener((o,oldVal,newVal) -> updateDrawerAnimation(newVal.doubleValue())); - - // content listener for mouse hold on a side - this.content.addEventHandler(MouseEvent.MOUSE_PRESSED, (e) -> { - if(!e.isConsumed()){ - double size = 0 ; - long valid = 0; - for (int i =0 ; i < callBacks.size(); i++) - if(!callBacks.get(i).call(null)) valid++; - // long valid = callBacks.stream().filter(callback->!callback.call(null)).count(); - if(directionProperty.get().equals(DrawerDirection.RIGHT)) size = content.getWidth(); - else if(directionProperty.get().equals(DrawerDirection.BOTTOM)) size = content.getHeight(); - - double eventPoint = 0; - if(directionProperty.get().equals(DrawerDirection.RIGHT) || directionProperty.get().equals(DrawerDirection.LEFT)) eventPoint = e.getX(); - else eventPoint = e.getY(); - - if(size + directionProperty.get().doubleValue() * eventPoint < activeOffset && (content.getCursor() == Cursor.DEFAULT || content.getCursor() == null) && valid == 0){ - holdTimer.play(); - e.consume(); - } - } - }); - - // mouse drag handler - translateProperty.addListener((o,oldVal,newVal)->{ - double opValue = 1-newVal.doubleValue()/initTranslate.doubleValue(); - overlayPane.setOpacity(opValue); - }); - - // add opening/closing action listeners - translateProperty.addListener((o,oldVal,newVal)->{ - if(!openCalled && closeCalled && directionProperty.get().doubleValue() * newVal.doubleValue() > directionProperty.get().doubleValue() * initTranslate.doubleValue() /2){ - openCalled = true; - closeCalled = false; - onDrawerOpeningProperty.get().handle(new JFXDrawerEvent(JFXDrawerEvent.OPENING)); - } - }); - translateProperty.addListener((o,oldVal,newVal)->{ - if(openCalled && !closeCalled && directionProperty.get().doubleValue() * newVal.doubleValue() < directionProperty.get().doubleValue() * initTranslate.doubleValue() /2){ - closeCalled = true; - openCalled = false; - onDrawerClosingProperty.get().handle(new JFXDrawerEvent(JFXDrawerEvent.CLOSING)); - } - }); - - - - this.sidePane.addEventHandler(MouseEvent.MOUSE_DRAGGED,mouseDragHandler); - this.sidePane.addEventHandler(MouseEvent.MOUSE_RELEASED,mouseReleasedHandler); - this.sidePane.addEventHandler(MouseEvent.MOUSE_PRESSED,mousePressedHandler); - - this.content.addEventHandler(MouseEvent.MOUSE_RELEASED, (e) -> { - holdTimer.stop(); - this.content.removeEventFilter(MouseEvent.MOUSE_DRAGGED,mouseDragHandler); - }); - - holdTimer.setOnFinished((e)->{ - if(!this.getChildren().contains(overlayPane)) this.getChildren().add(overlayPane); - if(!this.getChildren().contains(sidePane)) this.getChildren().add(sidePane); - partialTransition = new DrawerPartialTransition(initTranslate.doubleValue(), initTranslate.doubleValue() + initOffset * directionProperty.get().doubleValue() + activeOffset * directionProperty.get().doubleValue()); - partialTransition.setOnFinished((event)-> { - this.content.addEventFilter(MouseEvent.MOUSE_DRAGGED,mouseDragHandler); - this.content.addEventHandler(MouseEvent.MOUSE_RELEASED, mouseReleasedHandler); - this.content.addEventHandler(MouseEvent.MOUSE_RELEASED, new EventHandler() { - @Override - public void handle(Event event) { - JFXDrawer.this.content.removeEventHandler(MouseEvent.MOUSE_RELEASED, mouseReleasedHandler); - JFXDrawer.this.content.removeEventHandler(MouseEvent.MOUSE_RELEASED, this); - } - }); - }); - partialTransition.play(); - }); - } - - - ChangeListener widthListener = (o,oldVal,newVal) -> {if(newVal!=null && newVal instanceof Region) parentSizeProperty.set(((Region)newVal).widthProperty());}; - ChangeListener heightListener = (o,oldVal,newVal) -> {if(newVal!=null && newVal instanceof Region) parentSizeProperty.set(((Region)newVal).heightProperty());}; - ChangeListener sceneWidthListener = (o,oldVal,newVal)->{ if(newVal!=null && this.getParent()==null) parentSizeProperty.set(newVal.widthProperty());}; - ChangeListener sceneHeightListener = (o,oldVal,newVal)->{ if(newVal!=null && this.getParent()==null) parentSizeProperty.set(newVal.heightProperty());}; - /** - * this method will change the drawer behavior according to its direction - * @param dir - */ - private final void updateDirection(DrawerDirection dir){ - maxSizeProperty.get().set(-1); - prefSizeProperty.get().set(-1); - - if(dir.equals(DrawerDirection.LEFT)){ - // change the pane position - StackPane.setAlignment(sidePane, Pos.CENTER_LEFT); - // reset old translation - translateProperty.set(0); - // set the new translation property - translateProperty = sidePane.translateXProperty(); - // change the size property - maxSizeProperty.set(sidePane.maxWidthProperty()); - minSizeProperty.set(sidePane.minWidthProperty()); - prefSizeProperty.set(sidePane.prefWidthProperty()); - sizeProperty.set(sidePane.widthProperty()); - this.boundedNodeProperty().removeListener(heightListener); - this.boundedNodeProperty().addListener(widthListener); - if(getBoundedNode()==null) this.boundedNodeProperty().bind(this.parentProperty()); - this.sceneProperty().removeListener(sceneHeightListener); - this.sceneProperty().removeListener(sceneWidthListener); - this.sceneProperty().addListener(sceneWidthListener); - }else if(dir.equals(DrawerDirection.RIGHT)){ - StackPane.setAlignment(sidePane, Pos.CENTER_RIGHT); - translateProperty.set(0); - translateProperty = sidePane.translateXProperty(); - maxSizeProperty.set(sidePane.maxWidthProperty()); - minSizeProperty.set(sidePane.minWidthProperty()); - prefSizeProperty.set(sidePane.prefWidthProperty()); - sizeProperty.set(sidePane.widthProperty()); - this.boundedNodeProperty().removeListener(heightListener); - this.boundedNodeProperty().addListener(widthListener); - if(getBoundedNode()==null) this.boundedNodeProperty().bind(this.parentProperty()); - this.sceneProperty().removeListener(sceneHeightListener); - this.sceneProperty().removeListener(sceneWidthListener); - this.sceneProperty().addListener(sceneWidthListener); - }else if(dir.equals(DrawerDirection.TOP)){ - StackPane.setAlignment(sidePane, Pos.TOP_CENTER); - translateProperty.set(0); - translateProperty = sidePane.translateYProperty(); - maxSizeProperty.set(sidePane.maxHeightProperty()); - minSizeProperty.set(sidePane.minHeightProperty()); - prefSizeProperty.set(sidePane.prefHeightProperty()); - sizeProperty.set(sidePane.heightProperty()); - this.boundedNodeProperty().removeListener(widthListener); - this.boundedNodeProperty().addListener(heightListener); - if(getBoundedNode()==null) this.boundedNodeProperty().bind(this.parentProperty()); - this.sceneProperty().removeListener(sceneHeightListener); - this.sceneProperty().removeListener(sceneWidthListener); - this.sceneProperty().addListener(sceneHeightListener); - }else if(dir.equals(DrawerDirection.BOTTOM)){ - StackPane.setAlignment(sidePane, Pos.BOTTOM_CENTER); - translateProperty.set(0); - translateProperty = sidePane.translateYProperty(); - maxSizeProperty.set(sidePane.maxHeightProperty()); - minSizeProperty.set(sidePane.minHeightProperty()); - prefSizeProperty.set(sidePane.prefHeightProperty()); - sizeProperty.set(sidePane.heightProperty()); - this.boundedNodeProperty().removeListener(widthListener); - this.boundedNodeProperty().addListener(heightListener); - if(getBoundedNode()==null) this.boundedNodeProperty().bind(this.parentProperty()); - this.sceneProperty().removeListener(sceneHeightListener); - this.sceneProperty().removeListener(sceneWidthListener); - this.sceneProperty().addListener(sceneHeightListener); - } - setDefaultDrawerSize(defaultSizeProperty.get()); - updateDrawerAnimation(initTranslate.doubleValue()); - } - - private final void updateDrawerAnimation(double translation){ - drawerTransition = new DrawerTransition(translation, 0); - // outTransition = new OutDrawerTransition(translation,0); - translateProperty.set(translation); - } - - /*************************************************************************** - * * - * Public API * - * * - **************************************************************************/ - - private ArrayList> callBacks = new ArrayList<>(); - - /** - * the callbacks are used to add conditions to allow - * starting the drawer when holding on the side part of the content - */ - public void addInitDrawerCallback(Callback callBack){ - callBacks.add(callBack); - } - - /** - * this method is only used in drawers stack component - * @param callback - */ - void bringToFront(Callback callback){ - - EventHandler eventFilter = (event)->event.consume(); - final boolean bindSize = prefSizeProperty.get().isBound(); - prefSizeProperty.get().unbind(); - maxSizeProperty.get().unbind(); - // disable mouse events - this.addEventFilter(MouseEvent.ANY, eventFilter); - - EventHandler drawerDrawer = (finish)->{ - // outTransition.setOnFinished(null); - callback.call(null); - - if(this.drawerTransition.getStatus().equals(Status.STOPPED) && translateProperty.get() != 0){ - drawerTransition.setRate(1); - if(tempDrawerSize > getDefaultDrawerSize()) { - ParallelTransition parallelTransition = new ParallelTransition(new InDrawerSizeTransition(), new DrawerTransition(translateProperty.get(), 0)); - parallelTransition.setOnFinished((finish1)->{ - if(bindSize){ - prefSizeProperty.get().bind(parentSizeProperty.get()); - maxSizeProperty.get().bind(parentSizeProperty.get()); - } - }); - parallelTransition.play(); - }else { - EventHandler oldFinishHandler = this.drawerTransition.getOnFinished(); - this.drawerTransition.setOnFinished((finish1)->{ - if(bindSize){ - prefSizeProperty.get().bind(parentSizeProperty.get()); - maxSizeProperty.get().bind(parentSizeProperty.get()); - } - this.drawerTransition.setOnFinished(oldFinishHandler);}); - this.drawerTransition.play(); - } - } - // enable mouse events - this.removeEventFilter(MouseEvent.ANY, eventFilter); - }; - - if(sizeProperty.get().get() > getDefaultDrawerSize()){ - tempDrawerSize = sizeProperty.get().get(); - ParallelTransition parallelTransition = new ParallelTransition(new OutDrawerSizeTransition(), new DrawerTransition(translateProperty.get(), initTranslate.doubleValue())); - parallelTransition.setOnFinished(drawerDrawer); - parallelTransition.play(); - }else{ - if(drawerTransition.getStatus().equals(Status.STOPPED) && translateProperty.get() == 0) { - drawerTransition.setRate(-1); - drawerTransition.setOnFinished(drawerDrawer); - drawerTransition.play(); - } - tempDrawerSize = getDefaultDrawerSize(); - } - } - - /** - * this method indicates whether the drawer is shown or not - * @return true if he drawer is totally visible else false - */ - public boolean isShown() { - if(this.drawerTransition.getStatus().equals(Status.STOPPED) && translateProperty.get() == 0) return true; - return false; - } - public boolean isShowing(){ - if((this.drawerTransition.getStatus().equals(Status.RUNNING) && this.drawerTransition.getRate() > 0) || (partialTransition !=null && partialTransition instanceof DrawerPartialTransitionDraw && partialTransition.getStatus().equals(Status.RUNNING))) return true; - return false; - } - public boolean isHidding(){ - if((this.drawerTransition.getStatus().equals(Status.RUNNING) && this.drawerTransition.getRate() < 0) || (partialTransition !=null && partialTransition instanceof DrawerPartialTransitionHide && partialTransition.getStatus().equals(Status.RUNNING))) return true; - return false; - } - public boolean isHidden(){ - return !this.getChildren().contains(this.overlayPane); - } - - /** - * toggle the drawer on - */ - public void open() { - if(((partialTransition!=null && partialTransition.getStatus().equals(Status.STOPPED)) || partialTransition == null)){ - drawerTransition.setRate(1); - this.drawerTransition.setOnFinished((finish)-> onDrawerOpenedProperty.get().handle(new JFXDrawerEvent(JFXDrawerEvent.OPENED))); - if(this.drawerTransition.getStatus().equals(Status.STOPPED)){ - if(isHidden()) this.drawerTransition.playFromStart(); - else this.drawerTransition.play(); - } - }else{ - partialOpen(); - } - } - - /** - * toggle the drawer off - */ - public void close(){ - // unbind properties as the drawer size might be bound to stage size - maxSizeProperty.get().unbind(); - prefSizeProperty.get().unbind(); - - if(sizeProperty.get().get() > getDefaultDrawerSize()){ - tempDrawerSize = prefSizeProperty.get().get(); - ParallelTransition parallelTransition = new ParallelTransition(new OutDrawerSizeTransition(), new DrawerTransition(translateProperty.get(), initTranslate.doubleValue())); - parallelTransition.setOnFinished((finish)->{ - onDrawerClosedProperty.get().handle(new JFXDrawerEvent(JFXDrawerEvent.CLOSED)); - }); - parallelTransition.play(); - }else{ - if(((partialTransition!=null && partialTransition.getStatus().equals(Status.STOPPED)) || partialTransition == null)){ - drawerTransition.setRate(-1); - drawerTransition.setOnFinished((finish)-> onDrawerClosedProperty.get().handle(new JFXDrawerEvent(JFXDrawerEvent.CLOSED))); - if(this.drawerTransition.getStatus().equals(Status.STOPPED)){ - if(isShown()) this.drawerTransition.playFrom(this.drawerTransition.getCycleDuration()); - else this.drawerTransition.play(); - } - }else{ - partialClose(); - } - tempDrawerSize = getDefaultDrawerSize(); - } - } - - /*************************************************************************** - * * - * Setters / Getters * - * * - **************************************************************************/ - - public ObservableList getSidePane() { - return sidePane.getChildren(); - } - - public void setSidePane(Node... sidePane) { - this.sidePane.getChildren().setAll(sidePane); - } - - public ObservableList getContent() { - return content.getChildren(); - } - - public void setContent(Node... content) { - this.content.getChildren().setAll(content); - } - - public double getDefaultDrawerSize() { - return defaultSizeProperty.get(); - } - - public void setDefaultDrawerSize(double drawerWidth) { - defaultSizeProperty.set(drawerWidth); - maxSizeProperty.get().set(drawerWidth); - prefSizeProperty.get().set(drawerWidth); - } - - public DrawerDirection getDirection() { - return directionProperty.get(); - } - - public SimpleObjectProperty directionProperty(){ - return directionProperty; - } - - public void setDirection(DrawerDirection direction) { - this.directionProperty.set(direction); - } - - public final BooleanProperty overLayVisibleProperty() { - return this.overLayVisible; - } - - public final boolean isOverLayVisible() { - return this.overLayVisibleProperty().get(); - } - - public final void setOverLayVisible(final boolean overLayVisible) { - this.overLayVisibleProperty().set(overLayVisible); - } - - public boolean isResizableOnDrag(){ - return resizable; - } - - public void setResizableOnDrag(boolean resizable){ - this.resizable = resizable; - } - - public final ObjectProperty boundedNodeProperty() { - return this.boundedNode; - } - - public final Node getBoundedNode() { - return this.boundedNodeProperty().get(); - } - - public final void setBoundedNode(final Node boundedNode) { - this.boundedNodeProperty().unbind(); - this.boundedNodeProperty().set(boundedNode); - } - - - /*************************************************************************** - * * - * Custom Events * - * * - **************************************************************************/ - - private ObjectProperty> onDrawerClosedProperty = new SimpleObjectProperty<>((closed)->{}); - - public void setOnDrawerClosed(EventHandler handler){ - onDrawerClosedProperty.set(handler); - } - - public void getOnDrawerClosed(EventHandler handler){ - onDrawerClosedProperty.get(); - } - - /** - * Defines a function to be called when the drawer is closing. - */ - private ObjectProperty> onDrawerClosingProperty = new SimpleObjectProperty<>((closing)->{}); - - public void setOnDrawerClosing(EventHandler handler){ - onDrawerClosingProperty.set(handler); - } - - public void getOnDrawerClosing(EventHandler handler){ - onDrawerClosingProperty.get(); - } - - - private ObjectProperty> onDrawerOpenedProperty = new SimpleObjectProperty<>((opened)->{}); - - public void setOnDrawerOpened(EventHandler handler){ - onDrawerOpenedProperty.set(handler); - } - - public void getOnDrawerOpened(EventHandler handler){ - onDrawerOpenedProperty.get(); - } - - /** - * Defines a function to be called when the drawer is opening. - */ - private ObjectProperty> onDrawerOpeningProperty = new SimpleObjectProperty<>((opening)->{}); - - public void setOnDrawerOpening(EventHandler handler){ - onDrawerOpeningProperty.set(handler); - } - - public void getOnDrawerOpening(EventHandler handler){ - onDrawerOpeningProperty.get(); - } - - - /*************************************************************************** - * * - * Action Handlers * - * * - **************************************************************************/ - - private EventHandler mouseDragHandler = (mouseEvent)->{ - if(!mouseEvent.isConsumed()){ - mouseEvent.consume(); - double size = 0 ; - Bounds sceneBounds = content.localToScene(content.getLayoutBounds()); - if(directionProperty.get().equals(DrawerDirection.RIGHT)) size = sceneBounds.getMinX() + sceneBounds.getWidth(); - else if(directionProperty.get().equals(DrawerDirection.BOTTOM)) size = sceneBounds.getMinY() + sceneBounds.getHeight(); - - if(startSize == -1) startSize = sizeProperty.get().get(); - - double eventPoint = 0; - if(directionProperty.get().equals(DrawerDirection.RIGHT) || directionProperty.get().equals(DrawerDirection.LEFT)) eventPoint = mouseEvent.getSceneX(); - else eventPoint = mouseEvent.getSceneY(); - - if(size + directionProperty.get().doubleValue() * eventPoint >= activeOffset && partialTransition !=null){ - partialTransition = null; - }else if(partialTransition == null){ - double currentTranslate ; - if(startMouse < 0) currentTranslate = initTranslate.doubleValue() + directionProperty.get().doubleValue() * initOffset + directionProperty.get().doubleValue() * (size + directionProperty.get().doubleValue() * eventPoint); - else currentTranslate = directionProperty.get().doubleValue() * (startTranslate + directionProperty.get().doubleValue() * ( eventPoint - startMouse )); - - if(directionProperty.get().doubleValue() * currentTranslate <= 0){ - // the drawer is hidden - if(resizable){ - maxSizeProperty.get().unbind(); - prefSizeProperty.get().unbind(); - if((startSize - getDefaultDrawerSize()) + directionProperty.get().doubleValue() * currentTranslate > 0){ - // change the side drawer size if dragging from hidden - maxSizeProperty.get().set(startSize + directionProperty.get().doubleValue() * currentTranslate); - prefSizeProperty.get().set(startSize + directionProperty.get().doubleValue() * currentTranslate); - }else{ - // if the side drawer is not fully shown perform translation to show it , and set its default size - maxSizeProperty.get().set(defaultSizeProperty.get()); - maxSizeProperty.get().set(defaultSizeProperty.get()); - translateProperty.set(directionProperty.get().doubleValue() * ((startSize - getDefaultDrawerSize()) + directionProperty.get().doubleValue() * currentTranslate)); - } - }else - translateProperty.set(currentTranslate); - }else{ - // the drawer is already shown - if(resizable){ - if(startSize + directionProperty.get().doubleValue() * currentTranslate <= parentSizeProperty.get().get()){ - // change the side drawer size after being shown - maxSizeProperty.get().unbind(); - prefSizeProperty.get().unbind(); - maxSizeProperty.get().set(startSize + directionProperty.get().doubleValue() * currentTranslate); - prefSizeProperty.get().set(startSize + directionProperty.get().doubleValue() * currentTranslate); - }else{ - // bind the drawer size to its parent - maxSizeProperty.get().bind(parentSizeProperty.get()); - prefSizeProperty.get().bind(parentSizeProperty.get()); - } - } - translateProperty.set(0); - } - } - } - }; - - private EventHandler mousePressedHandler = (mouseEvent)->{ - if(directionProperty.get().equals(DrawerDirection.RIGHT) || directionProperty.get().equals(DrawerDirection.LEFT)) startMouse = mouseEvent.getSceneX(); - else startMouse = mouseEvent.getSceneY(); - startTranslate = translateProperty.get(); - startSize = sizeProperty.get().get(); - - }; - - private EventHandler mouseReleasedHandler = (mouseEvent)->{ - if(directionProperty.get().doubleValue() * translateProperty.get() > directionProperty.get().doubleValue() * initTranslate.doubleValue() /2){ - // show side pane - partialOpen(); - }else{ - // hide the sidePane - partialClose(); - } - // reset drawer animation properties - startMouse = -1; - startTranslate = -1; - startSize = sizeProperty.get().get(); - }; - - private void partialClose(){ - if(partialTransition!=null) partialTransition.stop(); - partialTransition = new DrawerPartialTransitionHide(translateProperty.get(), initTranslate.doubleValue()); - partialTransition.setOnFinished((event)-> { - this.getChildren().remove(overlayPane); - this.getChildren().remove(sidePane); - translateProperty.set(initTranslate.doubleValue()); - onDrawerClosedProperty.get().handle(new JFXDrawerEvent(JFXDrawerEvent.CLOSED)); - }); - partialTransition.play(); - } - - private void partialOpen(){ - if(partialTransition!=null) partialTransition.stop(); - partialTransition = new DrawerPartialTransitionDraw(translateProperty.get(), 0); - partialTransition.setOnFinished((event)->{ - translateProperty.set(0); - onDrawerOpenedProperty.get().handle(new JFXDrawerEvent(JFXDrawerEvent.OPENED)); - }); - partialTransition.play(); - } - - /*************************************************************************** - * * - * Animations * - * * - **************************************************************************/ - - private double tempDrawerSize = getDefaultDrawerSize(); - - private class OutDrawerSizeTransition extends CachedTransition{ - public OutDrawerSizeTransition() { - super(sidePane, new Timeline(new KeyFrame(Duration.millis(1000), - new KeyValue(prefSizeProperty.get(), getDefaultDrawerSize(),Interpolator.EASE_BOTH), - new KeyValue(maxSizeProperty.get(), getDefaultDrawerSize(),Interpolator.EASE_BOTH))) - ); - setCycleDuration(Duration.seconds(0.5)); - setDelay(Duration.seconds(0)); - } - } - - private class InDrawerSizeTransition extends CachedTransition{ - public InDrawerSizeTransition() { - super(sidePane, new Timeline( - new KeyFrame(Duration.millis(0), - new KeyValue(maxSizeProperty.get(), getDefaultDrawerSize(),Interpolator.EASE_BOTH), - new KeyValue(prefSizeProperty.get(), getDefaultDrawerSize(),Interpolator.EASE_BOTH)), - new KeyFrame(Duration.millis(1000), - new KeyValue(maxSizeProperty.get(), tempDrawerSize,Interpolator.EASE_BOTH), - new KeyValue(prefSizeProperty.get(), tempDrawerSize,Interpolator.EASE_BOTH) - ))); - setCycleDuration(Duration.seconds(0.5)); - setDelay(Duration.seconds(0)); - } - } - - - private class DrawerTransition extends CachedTransition{ - public DrawerTransition(double start, double end) { - super(sidePane, new Timeline( - new KeyFrame(Duration.ZERO, new KeyValue(translateProperty, start ,Interpolator.EASE_BOTH)), - new KeyFrame(Duration.millis(1000), new KeyValue(translateProperty, end , Interpolator.EASE_BOTH)) - /* - * TODO: add this in later java version, as currently - * Region is not rendered correctly when node cache is enabled - * and this issue will be fixed later. - */ - )/*, new CacheMomento(overlayPane)*/); - setCycleDuration(Duration.seconds(0.5)); - setDelay(Duration.seconds(0)); - } - @Override - protected void starting() { - super.starting(); - if(this.getRate() > 0){ - if(!JFXDrawer.this.getChildren().contains(overlayPane)) JFXDrawer.this.getChildren().add(overlayPane); - if(!JFXDrawer.this.getChildren().contains(sidePane)) JFXDrawer.this.getChildren().add(sidePane); - } - } - @Override - protected void stopping() { - super.stopping(); - if(this.getRate() < 0){ - JFXDrawer.this.getChildren().remove(overlayPane); - JFXDrawer.this.getChildren().remove(sidePane); - } - } - } - - private class DrawerPartialTransition extends CachedTransition{ - public DrawerPartialTransition(double start, double end) { - super(sidePane, new Timeline( - new KeyFrame(Duration.ZERO, new KeyValue(translateProperty, start ,Interpolator.EASE_BOTH)), - new KeyFrame(Duration.millis(600), new KeyValue(translateProperty, end , Interpolator.EASE_BOTH)))); - setCycleDuration(Duration.seconds(0.5)); - setDelay(Duration.seconds(0)); - } - } - private class DrawerPartialTransitionDraw extends DrawerPartialTransition{ - public DrawerPartialTransitionDraw(double start, double end) { - super(start, end); - } - } - private class DrawerPartialTransitionHide extends DrawerPartialTransition{ - public DrawerPartialTransitionHide(double start, double end) { - super(start, end); - } - } - - /*************************************************************************** - * * - * Stylesheet Handling * - * * - **************************************************************************/ - - /** - * Initialize the style class to 'jfx-drawer'. - * - * This is the selector class from which CSS can be used to style - * this control. - */ - private static final String DEFAULT_STYLE_CLASS = "jfx-drawer"; - -} - +/* + * 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 com.jfoenix.controls.events.JFXDrawerEvent; +import com.jfoenix.transitions.CachedTransition; +import javafx.animation.Animation.Status; +import javafx.animation.*; +import javafx.beans.binding.Bindings; +import javafx.beans.property.*; +import javafx.beans.value.ChangeListener; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.event.Event; +import javafx.event.EventHandler; +import javafx.geometry.Bounds; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Cursor; +import javafx.scene.Node; +import javafx.scene.Scene; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.*; +import javafx.scene.paint.Color; +import javafx.util.Callback; +import javafx.util.Duration; + +import java.util.ArrayList; + +/** + * JFXDrawer is material design implementation of drawer. + * the drawer has two main nodes, the content and side pane. + *
    + *
  • content pane: is a stack pane that holds the nodes inside the drawer
  • + *
  • side pane: is a stack pane that holds the nodes inside the drawer side area (Drawable node)
  • + *
+ * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXDrawer extends StackPane { + + public static enum DrawerDirection{ + LEFT(1), RIGHT(-1), TOP(1), BOTTOM(-1); + private double numVal; + DrawerDirection(double numVal) { + this.numVal = numVal; + } + public double doubleValue() { + return numVal; + } + }; + + private StackPane overlayPane = new StackPane(); + StackPane sidePane = new StackPane(); + private StackPane content = new StackPane(); + private Transition drawerTransition; + // private Transition outTransition; + private Transition partialTransition; + private Duration holdTime = Duration.seconds(0.2); + private PauseTransition holdTimer = new PauseTransition(holdTime); + + private double initOffset = 30; + private DoubleProperty initTranslate = new SimpleDoubleProperty(); + private BooleanProperty overLayVisible = new SimpleBooleanProperty(true); + private double activeOffset = 20; + private double startMouse = -1; + private double startTranslate = -1; + private double startSize = -1; + private DoubleProperty translateProperty = sidePane.translateXProperty(); + private boolean resizable = false; + private boolean openCalled = false; + private boolean closeCalled = true; + + private DoubleProperty defaultSizeProperty = new SimpleDoubleProperty(); + private ObjectProperty maxSizeProperty = new SimpleObjectProperty<>(sidePane.maxWidthProperty()); + private ObjectProperty minSizeProperty = new SimpleObjectProperty<>(sidePane.minWidthProperty()); + private ObjectProperty prefSizeProperty = new SimpleObjectProperty<>(sidePane.prefWidthProperty()); + private ObjectProperty sizeProperty = new SimpleObjectProperty<>(sidePane.widthProperty()); + private ObjectProperty parentSizeProperty = new SimpleObjectProperty<>(); + private ObjectProperty boundedNode = new SimpleObjectProperty<>(); + + private SimpleObjectProperty directionProperty = new SimpleObjectProperty(DrawerDirection.LEFT); + + /** + * creates empy drawer node + */ + public JFXDrawer(){ + super(); + initialize(); + + overlayPane.setBackground(new Background(new BackgroundFill(Color.rgb(0, 0, 0, 0.1), CornerRadii.EMPTY, Insets.EMPTY))); + overlayPane.getStyleClass().add("jfx-drawer-overlay-pane"); + overlayPane.setOpacity(0); + + sidePane.getStyleClass().add("jfx-drawer-side-pane"); + sidePane.setBackground(new Background(new BackgroundFill(Color.rgb(255, 255, 255, 1), CornerRadii.EMPTY, Insets.EMPTY))); + sidePane.setPickOnBounds(false); + // causes performance issue when animating the drawer +// JFXDepthManager.setDepth(sidePane, 2); + + this.getChildren().add(content); + + // add listeners + overlayPane.setOnMouseClicked((e) -> close()); + initListeners(); + + // init size value + setDefaultDrawerSize(100); + } + + private void initialize() { + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + } + + private void initListeners(){ + updateDirection(directionProperty.get()); + initTranslate.bind(Bindings.createDoubleBinding(()-> -1 * directionProperty.get().doubleValue() * defaultSizeProperty.getValue() - initOffset * directionProperty.get().doubleValue(), defaultSizeProperty, directionProperty )); + + // add listeners to update drawer properties + overLayVisibleProperty().addListener((o,oldVal,newVal)->{ + overlayPane.setStyle(!newVal?"-fx-background-color : transparent;":""); + overlayPane.setMouseTransparent(!newVal); + overlayPane.setPickOnBounds(newVal); + }); + + directionProperty.addListener((o,oldVal,newVal)-> updateDirection(newVal)); + initTranslate.addListener((o,oldVal,newVal) -> updateDrawerAnimation(newVal.doubleValue())); + + // content listener for mouse hold on a side + this.content.addEventHandler(MouseEvent.MOUSE_PRESSED, (e) -> { + if(!e.isConsumed()){ + double size = 0 ; + long valid = 0; + for (int i =0 ; i < callBacks.size(); i++) + if(!callBacks.get(i).call(null)) valid++; + // long valid = callBacks.stream().filter(callback->!callback.call(null)).count(); + if(directionProperty.get().equals(DrawerDirection.RIGHT)) size = content.getWidth(); + else if(directionProperty.get().equals(DrawerDirection.BOTTOM)) size = content.getHeight(); + + double eventPoint = 0; + if(directionProperty.get().equals(DrawerDirection.RIGHT) || directionProperty.get().equals(DrawerDirection.LEFT)) eventPoint = e.getX(); + else eventPoint = e.getY(); + + if(size + directionProperty.get().doubleValue() * eventPoint < activeOffset && (content.getCursor() == Cursor.DEFAULT || content.getCursor() == null) && valid == 0){ + holdTimer.play(); + e.consume(); + } + } + }); + + // mouse drag handler + translateProperty.addListener((o,oldVal,newVal)->{ + double opValue = 1-newVal.doubleValue()/initTranslate.doubleValue(); + overlayPane.setOpacity(opValue); + }); + + // add opening/closing action listeners + translateProperty.addListener((o,oldVal,newVal)->{ + if(!openCalled && closeCalled && directionProperty.get().doubleValue() * newVal.doubleValue() > directionProperty.get().doubleValue() * initTranslate.doubleValue() /2){ + openCalled = true; + closeCalled = false; + onDrawerOpeningProperty.get().handle(new JFXDrawerEvent(JFXDrawerEvent.OPENING)); + } + }); + translateProperty.addListener((o,oldVal,newVal)->{ + if(openCalled && !closeCalled && directionProperty.get().doubleValue() * newVal.doubleValue() < directionProperty.get().doubleValue() * initTranslate.doubleValue() /2){ + closeCalled = true; + openCalled = false; + onDrawerClosingProperty.get().handle(new JFXDrawerEvent(JFXDrawerEvent.CLOSING)); + } + }); + + + + this.sidePane.addEventHandler(MouseEvent.MOUSE_DRAGGED,mouseDragHandler); + this.sidePane.addEventHandler(MouseEvent.MOUSE_RELEASED,mouseReleasedHandler); + this.sidePane.addEventHandler(MouseEvent.MOUSE_PRESSED,mousePressedHandler); + + this.content.addEventHandler(MouseEvent.MOUSE_RELEASED, (e) -> { + holdTimer.stop(); + this.content.removeEventFilter(MouseEvent.MOUSE_DRAGGED,mouseDragHandler); + }); + + holdTimer.setOnFinished((e)->{ + if(!this.getChildren().contains(overlayPane)) this.getChildren().add(overlayPane); + if(!this.getChildren().contains(sidePane)) this.getChildren().add(sidePane); + partialTransition = new DrawerPartialTransition(initTranslate.doubleValue(), initTranslate.doubleValue() + initOffset * directionProperty.get().doubleValue() + activeOffset * directionProperty.get().doubleValue()); + partialTransition.setOnFinished((event)-> { + this.content.addEventFilter(MouseEvent.MOUSE_DRAGGED,mouseDragHandler); + this.content.addEventHandler(MouseEvent.MOUSE_RELEASED, mouseReleasedHandler); + this.content.addEventHandler(MouseEvent.MOUSE_RELEASED, new EventHandler() { + @Override + public void handle(Event event) { + JFXDrawer.this.content.removeEventHandler(MouseEvent.MOUSE_RELEASED, mouseReleasedHandler); + JFXDrawer.this.content.removeEventHandler(MouseEvent.MOUSE_RELEASED, this); + } + }); + }); + partialTransition.play(); + }); + } + + + ChangeListener widthListener = (o,oldVal,newVal) -> {if(newVal!=null && newVal instanceof Region) parentSizeProperty.set(((Region)newVal).widthProperty());}; + ChangeListener heightListener = (o,oldVal,newVal) -> {if(newVal!=null && newVal instanceof Region) parentSizeProperty.set(((Region)newVal).heightProperty());}; + ChangeListener sceneWidthListener = (o,oldVal,newVal)->{ if(newVal!=null && this.getParent()==null) parentSizeProperty.set(newVal.widthProperty());}; + ChangeListener sceneHeightListener = (o,oldVal,newVal)->{ if(newVal!=null && this.getParent()==null) parentSizeProperty.set(newVal.heightProperty());}; + /** + * this method will change the drawer behavior according to its direction + * @param dir + */ + private final void updateDirection(DrawerDirection dir){ + maxSizeProperty.get().set(-1); + prefSizeProperty.get().set(-1); + + if(dir.equals(DrawerDirection.LEFT)){ + // change the pane position + StackPane.setAlignment(sidePane, Pos.CENTER_LEFT); + // reset old translation + translateProperty.set(0); + // set the new translation property + translateProperty = sidePane.translateXProperty(); + // change the size property + maxSizeProperty.set(sidePane.maxWidthProperty()); + minSizeProperty.set(sidePane.minWidthProperty()); + prefSizeProperty.set(sidePane.prefWidthProperty()); + sizeProperty.set(sidePane.widthProperty()); + this.boundedNodeProperty().removeListener(heightListener); + this.boundedNodeProperty().addListener(widthListener); + if(getBoundedNode()==null) this.boundedNodeProperty().bind(this.parentProperty()); + this.sceneProperty().removeListener(sceneHeightListener); + this.sceneProperty().removeListener(sceneWidthListener); + this.sceneProperty().addListener(sceneWidthListener); + }else if(dir.equals(DrawerDirection.RIGHT)){ + StackPane.setAlignment(sidePane, Pos.CENTER_RIGHT); + translateProperty.set(0); + translateProperty = sidePane.translateXProperty(); + maxSizeProperty.set(sidePane.maxWidthProperty()); + minSizeProperty.set(sidePane.minWidthProperty()); + prefSizeProperty.set(sidePane.prefWidthProperty()); + sizeProperty.set(sidePane.widthProperty()); + this.boundedNodeProperty().removeListener(heightListener); + this.boundedNodeProperty().addListener(widthListener); + if(getBoundedNode()==null) this.boundedNodeProperty().bind(this.parentProperty()); + this.sceneProperty().removeListener(sceneHeightListener); + this.sceneProperty().removeListener(sceneWidthListener); + this.sceneProperty().addListener(sceneWidthListener); + }else if(dir.equals(DrawerDirection.TOP)){ + StackPane.setAlignment(sidePane, Pos.TOP_CENTER); + translateProperty.set(0); + translateProperty = sidePane.translateYProperty(); + maxSizeProperty.set(sidePane.maxHeightProperty()); + minSizeProperty.set(sidePane.minHeightProperty()); + prefSizeProperty.set(sidePane.prefHeightProperty()); + sizeProperty.set(sidePane.heightProperty()); + this.boundedNodeProperty().removeListener(widthListener); + this.boundedNodeProperty().addListener(heightListener); + if(getBoundedNode()==null) this.boundedNodeProperty().bind(this.parentProperty()); + this.sceneProperty().removeListener(sceneHeightListener); + this.sceneProperty().removeListener(sceneWidthListener); + this.sceneProperty().addListener(sceneHeightListener); + }else if(dir.equals(DrawerDirection.BOTTOM)){ + StackPane.setAlignment(sidePane, Pos.BOTTOM_CENTER); + translateProperty.set(0); + translateProperty = sidePane.translateYProperty(); + maxSizeProperty.set(sidePane.maxHeightProperty()); + minSizeProperty.set(sidePane.minHeightProperty()); + prefSizeProperty.set(sidePane.prefHeightProperty()); + sizeProperty.set(sidePane.heightProperty()); + this.boundedNodeProperty().removeListener(widthListener); + this.boundedNodeProperty().addListener(heightListener); + if(getBoundedNode()==null) this.boundedNodeProperty().bind(this.parentProperty()); + this.sceneProperty().removeListener(sceneHeightListener); + this.sceneProperty().removeListener(sceneWidthListener); + this.sceneProperty().addListener(sceneHeightListener); + } + setDefaultDrawerSize(defaultSizeProperty.get()); + updateDrawerAnimation(initTranslate.doubleValue()); + } + + private final void updateDrawerAnimation(double translation){ + drawerTransition = new DrawerTransition(translation, 0); + // outTransition = new OutDrawerTransition(translation,0); + translateProperty.set(translation); + } + + /*************************************************************************** + * * + * Public API * + * * + **************************************************************************/ + + private ArrayList> callBacks = new ArrayList<>(); + + /** + * the callbacks are used to add conditions to allow + * starting the drawer when holding on the side part of the content + */ + public void addInitDrawerCallback(Callback callBack){ + callBacks.add(callBack); + } + + /** + * this method is only used in drawers stack component + * @param callback + */ + void bringToFront(Callback callback){ + + EventHandler eventFilter = (event)->event.consume(); + final boolean bindSize = prefSizeProperty.get().isBound(); + prefSizeProperty.get().unbind(); + maxSizeProperty.get().unbind(); + // disable mouse events + this.addEventFilter(MouseEvent.ANY, eventFilter); + + EventHandler drawerDrawer = (finish)->{ + // outTransition.setOnFinished(null); + callback.call(null); + + if(this.drawerTransition.getStatus().equals(Status.STOPPED) && translateProperty.get() != 0){ + drawerTransition.setRate(1); + if(tempDrawerSize > getDefaultDrawerSize()) { + ParallelTransition parallelTransition = new ParallelTransition(new InDrawerSizeTransition(), new DrawerTransition(translateProperty.get(), 0)); + parallelTransition.setOnFinished((finish1)->{ + if(bindSize){ + prefSizeProperty.get().bind(parentSizeProperty.get()); + maxSizeProperty.get().bind(parentSizeProperty.get()); + } + }); + parallelTransition.play(); + }else { + EventHandler oldFinishHandler = this.drawerTransition.getOnFinished(); + this.drawerTransition.setOnFinished((finish1)->{ + if(bindSize){ + prefSizeProperty.get().bind(parentSizeProperty.get()); + maxSizeProperty.get().bind(parentSizeProperty.get()); + } + this.drawerTransition.setOnFinished(oldFinishHandler);}); + this.drawerTransition.play(); + } + } + // enable mouse events + this.removeEventFilter(MouseEvent.ANY, eventFilter); + }; + + if(sizeProperty.get().get() > getDefaultDrawerSize()){ + tempDrawerSize = sizeProperty.get().get(); + ParallelTransition parallelTransition = new ParallelTransition(new OutDrawerSizeTransition(), new DrawerTransition(translateProperty.get(), initTranslate.doubleValue())); + parallelTransition.setOnFinished(drawerDrawer); + parallelTransition.play(); + }else{ + if(drawerTransition.getStatus().equals(Status.STOPPED) && translateProperty.get() == 0) { + drawerTransition.setRate(-1); + drawerTransition.setOnFinished(drawerDrawer); + drawerTransition.play(); + } + tempDrawerSize = getDefaultDrawerSize(); + } + } + + /** + * this method indicates whether the drawer is shown or not + * @return true if he drawer is totally visible else false + */ + public boolean isShown() { + if(this.drawerTransition.getStatus().equals(Status.STOPPED) && translateProperty.get() == 0) return true; + return false; + } + public boolean isShowing(){ + if((this.drawerTransition.getStatus().equals(Status.RUNNING) && this.drawerTransition.getRate() > 0) || (partialTransition !=null && partialTransition instanceof DrawerPartialTransitionDraw && partialTransition.getStatus().equals(Status.RUNNING))) return true; + return false; + } + public boolean isHidding(){ + if((this.drawerTransition.getStatus().equals(Status.RUNNING) && this.drawerTransition.getRate() < 0) || (partialTransition !=null && partialTransition instanceof DrawerPartialTransitionHide && partialTransition.getStatus().equals(Status.RUNNING))) return true; + return false; + } + public boolean isHidden(){ + return !this.getChildren().contains(this.overlayPane); + } + + /** + * toggle the drawer on + */ + public void open() { + if(((partialTransition!=null && partialTransition.getStatus().equals(Status.STOPPED)) || partialTransition == null)){ + drawerTransition.setRate(1); + this.drawerTransition.setOnFinished((finish)-> onDrawerOpenedProperty.get().handle(new JFXDrawerEvent(JFXDrawerEvent.OPENED))); + if(this.drawerTransition.getStatus().equals(Status.STOPPED)){ + if(isHidden()) this.drawerTransition.playFromStart(); + else this.drawerTransition.play(); + } + }else{ + partialOpen(); + } + } + + /** + * toggle the drawer off + */ + public void close(){ + // unbind properties as the drawer size might be bound to stage size + maxSizeProperty.get().unbind(); + prefSizeProperty.get().unbind(); + + if(sizeProperty.get().get() > getDefaultDrawerSize()){ + tempDrawerSize = prefSizeProperty.get().get(); + ParallelTransition parallelTransition = new ParallelTransition(new OutDrawerSizeTransition(), new DrawerTransition(translateProperty.get(), initTranslate.doubleValue())); + parallelTransition.setOnFinished((finish)->{ + onDrawerClosedProperty.get().handle(new JFXDrawerEvent(JFXDrawerEvent.CLOSED)); + }); + parallelTransition.play(); + }else{ + if(((partialTransition!=null && partialTransition.getStatus().equals(Status.STOPPED)) || partialTransition == null)){ + drawerTransition.setRate(-1); + drawerTransition.setOnFinished((finish)-> onDrawerClosedProperty.get().handle(new JFXDrawerEvent(JFXDrawerEvent.CLOSED))); + if(this.drawerTransition.getStatus().equals(Status.STOPPED)){ + if(isShown()) this.drawerTransition.playFrom(this.drawerTransition.getCycleDuration()); + else this.drawerTransition.play(); + } + }else{ + partialClose(); + } + tempDrawerSize = getDefaultDrawerSize(); + } + } + + /*************************************************************************** + * * + * Setters / Getters * + * * + **************************************************************************/ + + public ObservableList getSidePane() { + return sidePane.getChildren(); + } + + public void setSidePane(Node... sidePane) { + this.sidePane.getChildren().setAll(sidePane); + } + + public ObservableList getContent() { + return content.getChildren(); + } + + public void setContent(Node... content) { + this.content.getChildren().setAll(content); + } + + public double getDefaultDrawerSize() { + return defaultSizeProperty.get(); + } + + public void setDefaultDrawerSize(double drawerWidth) { + defaultSizeProperty.set(drawerWidth); + maxSizeProperty.get().set(drawerWidth); + prefSizeProperty.get().set(drawerWidth); + } + + public DrawerDirection getDirection() { + return directionProperty.get(); + } + + public SimpleObjectProperty directionProperty(){ + return directionProperty; + } + + public void setDirection(DrawerDirection direction) { + this.directionProperty.set(direction); + } + + public final BooleanProperty overLayVisibleProperty() { + return this.overLayVisible; + } + + public final boolean isOverLayVisible() { + return this.overLayVisibleProperty().get(); + } + + public final void setOverLayVisible(final boolean overLayVisible) { + this.overLayVisibleProperty().set(overLayVisible); + } + + public boolean isResizableOnDrag(){ + return resizable; + } + + public void setResizableOnDrag(boolean resizable){ + this.resizable = resizable; + } + + public final ObjectProperty boundedNodeProperty() { + return this.boundedNode; + } + + public final Node getBoundedNode() { + return this.boundedNodeProperty().get(); + } + + public final void setBoundedNode(final Node boundedNode) { + this.boundedNodeProperty().unbind(); + this.boundedNodeProperty().set(boundedNode); + } + + + /*************************************************************************** + * * + * Custom Events * + * * + **************************************************************************/ + + private ObjectProperty> onDrawerClosedProperty = new SimpleObjectProperty<>((closed)->{}); + + public void setOnDrawerClosed(EventHandler handler){ + onDrawerClosedProperty.set(handler); + } + + public void getOnDrawerClosed(EventHandler handler){ + onDrawerClosedProperty.get(); + } + + /** + * Defines a function to be called when the drawer is closing. + */ + private ObjectProperty> onDrawerClosingProperty = new SimpleObjectProperty<>((closing)->{}); + + public void setOnDrawerClosing(EventHandler handler){ + onDrawerClosingProperty.set(handler); + } + + public void getOnDrawerClosing(EventHandler handler){ + onDrawerClosingProperty.get(); + } + + + private ObjectProperty> onDrawerOpenedProperty = new SimpleObjectProperty<>((opened)->{}); + + public void setOnDrawerOpened(EventHandler handler){ + onDrawerOpenedProperty.set(handler); + } + + public void getOnDrawerOpened(EventHandler handler){ + onDrawerOpenedProperty.get(); + } + + /** + * Defines a function to be called when the drawer is opening. + */ + private ObjectProperty> onDrawerOpeningProperty = new SimpleObjectProperty<>((opening)->{}); + + public void setOnDrawerOpening(EventHandler handler){ + onDrawerOpeningProperty.set(handler); + } + + public void getOnDrawerOpening(EventHandler handler){ + onDrawerOpeningProperty.get(); + } + + + /*************************************************************************** + * * + * Action Handlers * + * * + **************************************************************************/ + + private EventHandler mouseDragHandler = (mouseEvent)->{ + if(!mouseEvent.isConsumed()){ + mouseEvent.consume(); + double size = 0 ; + Bounds sceneBounds = content.localToScene(content.getLayoutBounds()); + if(directionProperty.get().equals(DrawerDirection.RIGHT)) size = sceneBounds.getMinX() + sceneBounds.getWidth(); + else if(directionProperty.get().equals(DrawerDirection.BOTTOM)) size = sceneBounds.getMinY() + sceneBounds.getHeight(); + + if(startSize == -1) startSize = sizeProperty.get().get(); + + double eventPoint = 0; + if(directionProperty.get().equals(DrawerDirection.RIGHT) || directionProperty.get().equals(DrawerDirection.LEFT)) eventPoint = mouseEvent.getSceneX(); + else eventPoint = mouseEvent.getSceneY(); + + if(size + directionProperty.get().doubleValue() * eventPoint >= activeOffset && partialTransition !=null){ + partialTransition = null; + }else if(partialTransition == null){ + double currentTranslate ; + if(startMouse < 0) currentTranslate = initTranslate.doubleValue() + directionProperty.get().doubleValue() * initOffset + directionProperty.get().doubleValue() * (size + directionProperty.get().doubleValue() * eventPoint); + else currentTranslate = directionProperty.get().doubleValue() * (startTranslate + directionProperty.get().doubleValue() * ( eventPoint - startMouse )); + + if(directionProperty.get().doubleValue() * currentTranslate <= 0){ + // the drawer is hidden + if(resizable){ + maxSizeProperty.get().unbind(); + prefSizeProperty.get().unbind(); + if((startSize - getDefaultDrawerSize()) + directionProperty.get().doubleValue() * currentTranslate > 0){ + // change the side drawer size if dragging from hidden + maxSizeProperty.get().set(startSize + directionProperty.get().doubleValue() * currentTranslate); + prefSizeProperty.get().set(startSize + directionProperty.get().doubleValue() * currentTranslate); + }else{ + // if the side drawer is not fully shown perform translation to show it , and set its default size + maxSizeProperty.get().set(defaultSizeProperty.get()); + maxSizeProperty.get().set(defaultSizeProperty.get()); + translateProperty.set(directionProperty.get().doubleValue() * ((startSize - getDefaultDrawerSize()) + directionProperty.get().doubleValue() * currentTranslate)); + } + }else + translateProperty.set(currentTranslate); + }else{ + // the drawer is already shown + if(resizable){ + if(startSize + directionProperty.get().doubleValue() * currentTranslate <= parentSizeProperty.get().get()){ + // change the side drawer size after being shown + maxSizeProperty.get().unbind(); + prefSizeProperty.get().unbind(); + maxSizeProperty.get().set(startSize + directionProperty.get().doubleValue() * currentTranslate); + prefSizeProperty.get().set(startSize + directionProperty.get().doubleValue() * currentTranslate); + }else{ + // bind the drawer size to its parent + maxSizeProperty.get().bind(parentSizeProperty.get()); + prefSizeProperty.get().bind(parentSizeProperty.get()); + } + } + translateProperty.set(0); + } + } + } + }; + + private EventHandler mousePressedHandler = (mouseEvent)->{ + if(directionProperty.get().equals(DrawerDirection.RIGHT) || directionProperty.get().equals(DrawerDirection.LEFT)) startMouse = mouseEvent.getSceneX(); + else startMouse = mouseEvent.getSceneY(); + startTranslate = translateProperty.get(); + startSize = sizeProperty.get().get(); + + }; + + private EventHandler mouseReleasedHandler = (mouseEvent)->{ + if(directionProperty.get().doubleValue() * translateProperty.get() > directionProperty.get().doubleValue() * initTranslate.doubleValue() /2){ + // show side pane + partialOpen(); + }else{ + // hide the sidePane + partialClose(); + } + // reset drawer animation properties + startMouse = -1; + startTranslate = -1; + startSize = sizeProperty.get().get(); + }; + + private void partialClose(){ + if(partialTransition!=null) partialTransition.stop(); + partialTransition = new DrawerPartialTransitionHide(translateProperty.get(), initTranslate.doubleValue()); + partialTransition.setOnFinished((event)-> { + this.getChildren().remove(overlayPane); + this.getChildren().remove(sidePane); + translateProperty.set(initTranslate.doubleValue()); + onDrawerClosedProperty.get().handle(new JFXDrawerEvent(JFXDrawerEvent.CLOSED)); + }); + partialTransition.play(); + } + + private void partialOpen(){ + if(partialTransition!=null) partialTransition.stop(); + partialTransition = new DrawerPartialTransitionDraw(translateProperty.get(), 0); + partialTransition.setOnFinished((event)->{ + translateProperty.set(0); + onDrawerOpenedProperty.get().handle(new JFXDrawerEvent(JFXDrawerEvent.OPENED)); + }); + partialTransition.play(); + } + + /*************************************************************************** + * * + * Animations * + * * + **************************************************************************/ + + private double tempDrawerSize = getDefaultDrawerSize(); + + private class OutDrawerSizeTransition extends CachedTransition{ + public OutDrawerSizeTransition() { + super(sidePane, new Timeline(new KeyFrame(Duration.millis(1000), + new KeyValue(prefSizeProperty.get(), getDefaultDrawerSize(),Interpolator.EASE_BOTH), + new KeyValue(maxSizeProperty.get(), getDefaultDrawerSize(),Interpolator.EASE_BOTH))) + ); + setCycleDuration(Duration.seconds(0.5)); + setDelay(Duration.seconds(0)); + } + } + + private class InDrawerSizeTransition extends CachedTransition{ + public InDrawerSizeTransition() { + super(sidePane, new Timeline( + new KeyFrame(Duration.millis(0), + new KeyValue(maxSizeProperty.get(), getDefaultDrawerSize(),Interpolator.EASE_BOTH), + new KeyValue(prefSizeProperty.get(), getDefaultDrawerSize(),Interpolator.EASE_BOTH)), + new KeyFrame(Duration.millis(1000), + new KeyValue(maxSizeProperty.get(), tempDrawerSize,Interpolator.EASE_BOTH), + new KeyValue(prefSizeProperty.get(), tempDrawerSize,Interpolator.EASE_BOTH) + ))); + setCycleDuration(Duration.seconds(0.5)); + setDelay(Duration.seconds(0)); + } + } + + + private class DrawerTransition extends CachedTransition{ + public DrawerTransition(double start, double end) { + super(sidePane, new Timeline( + new KeyFrame(Duration.ZERO, new KeyValue(translateProperty, start ,Interpolator.EASE_BOTH)), + new KeyFrame(Duration.millis(1000), new KeyValue(translateProperty, end , Interpolator.EASE_BOTH)) + /* + * TODO: add this in later java version, as currently + * Region is not rendered correctly when node cache is enabled + * and this issue will be fixed later. + */ + )/*, new CacheMomento(overlayPane)*/); + setCycleDuration(Duration.seconds(0.5)); + setDelay(Duration.seconds(0)); + } + @Override + protected void starting() { + super.starting(); + if(this.getRate() > 0){ + if(!JFXDrawer.this.getChildren().contains(overlayPane)) JFXDrawer.this.getChildren().add(overlayPane); + if(!JFXDrawer.this.getChildren().contains(sidePane)) JFXDrawer.this.getChildren().add(sidePane); + } + } + @Override + protected void stopping() { + super.stopping(); + if(this.getRate() < 0){ + JFXDrawer.this.getChildren().remove(overlayPane); + JFXDrawer.this.getChildren().remove(sidePane); + } + } + } + + private class DrawerPartialTransition extends CachedTransition{ + public DrawerPartialTransition(double start, double end) { + super(sidePane, new Timeline( + new KeyFrame(Duration.ZERO, new KeyValue(translateProperty, start ,Interpolator.EASE_BOTH)), + new KeyFrame(Duration.millis(600), new KeyValue(translateProperty, end , Interpolator.EASE_BOTH)))); + setCycleDuration(Duration.seconds(0.5)); + setDelay(Duration.seconds(0)); + } + } + private class DrawerPartialTransitionDraw extends DrawerPartialTransition{ + public DrawerPartialTransitionDraw(double start, double end) { + super(start, end); + } + } + private class DrawerPartialTransitionHide extends DrawerPartialTransition{ + public DrawerPartialTransitionHide(double start, double end) { + super(start, end); + } + } + + /*************************************************************************** + * * + * Stylesheet Handling * + * * + **************************************************************************/ + + /** + * Initialize the style class to 'jfx-drawer'. + * + * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-drawer"; + +} + diff --git a/src/com/jfoenix/controls/JFXDrawersStack.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXDrawersStack.java similarity index 96% rename from src/com/jfoenix/controls/JFXDrawersStack.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXDrawersStack.java index c3944cc2..ce672e45 100644 --- a/src/com/jfoenix/controls/JFXDrawersStack.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/JFXDrawersStack.java @@ -1,165 +1,165 @@ -/* - * 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.application.Platform; -import javafx.beans.DefaultProperty; -import javafx.scene.Node; -import javafx.scene.input.MouseEvent; -import javafx.scene.layout.StackPane; -import javafx.scene.shape.Rectangle; - -import java.util.ArrayList; - -/** - * DrawersStack is used to show multiple drawers at once - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -@DefaultProperty(value="content") -public class JFXDrawersStack extends StackPane { - - private ArrayList drawers = new ArrayList<>(); - private Node content ; - private Rectangle clip = new Rectangle(); - boolean holding = false; - - /** - * creates empty drawers stack - */ - public JFXDrawersStack() { - super(); - clip.widthProperty().bind(this.widthProperty()); - clip.heightProperty().bind(this.heightProperty()); - this.setClip(clip); - } - - /** - * @return the content of the drawer stack (Note: the parent of the - * content node is that latest active drawer if existed) - */ - public Node getContent(){ - return this.content; - } - - /** - * set the content of the drawers stack, similar to {@link JFXDrawer#setContent(Node...)} - * @param content - */ - public void setContent(Node content){ - this.content = content; - if(drawers.size() > 0) drawers.get(0).setContent(content); - else this.getChildren().add(this.content); - } - - /** - * add JFXDrawer to the stack - * @param drawer - */ - private void addDrawer(JFXDrawer drawer){ - if (drawer == null) return; - - if(drawers.size() == 0){ - if(content!=null) drawer.setContent(content); - this.getChildren().add(drawer); - }else { - drawer.setContent(drawers.get(drawers.size()-1)); - this.getChildren().add(drawer); - } - - drawer.sidePane.addEventHandler(MouseEvent.MOUSE_PRESSED, (event)->{ - holding = true; - new Thread(()->{ - try { - Thread.sleep(300); - } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - if(holding){ - holding = false; - if(drawers.indexOf(drawer) < drawers.size()-1) - Platform.runLater(()->drawer.bringToFront((param)->{ - updateDrawerPosition(drawer); - return param; - })); - } - }).start(); - }); - - drawer.sidePane.addEventHandler(MouseEvent.MOUSE_DRAGGED, (event)-> holding = false); - drawer.sidePane.addEventHandler(MouseEvent.MOUSE_RELEASED, (event)-> holding = false); - - drawers.add(drawer); - } - - /** - * update drawers position in the stack once a drawer is drawn - * @param drawer - */ - private void updateDrawerPosition(JFXDrawer drawer){ - int index = drawers.indexOf(drawer); - if(index + 1 < drawers.size()){ - if(index - 1 >= 0) drawers.get(index+1).setContent(drawers.get(index-1)); - else if(index == 0) drawers.get(index+1).setContent(content); - } - if(index < drawers.size() - 1){ - drawer.setContent(drawers.get(drawers.size()-1)); - drawers.remove(drawer); - drawers.add(drawer); - this.getChildren().add(drawer); - } - } - - /** - * toggle a drawer in the stack - * - * @param drawer the drawer to be toggled - */ - public void toggle(JFXDrawer drawer){ - if(!drawers.contains(drawer)) addDrawer(drawer); - if(drawer.isShown() || drawer.isShowing()) drawer.close(); - else{ - updateDrawerPosition(drawer); - drawer.open(); - } - } - - /** - * toggle on/off a drawer in the stack - * - * @param drawer the drawer to be toggled - * @param show true to toggle on, false to toggle off - */ - public void toggle(JFXDrawer drawer, boolean show){ - if(!drawers.contains(drawer)) addDrawer(drawer); - if(!show){ - if(drawer.isShown() || drawer.isShowing()) drawer.close(); - }else{ - if(!drawer.isShown() && !drawer.isShowing()){ - updateDrawerPosition(drawer); - drawer.open(); - } - } - } - - -} +/* + * 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.application.Platform; +import javafx.beans.DefaultProperty; +import javafx.scene.Node; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.StackPane; +import javafx.scene.shape.Rectangle; + +import java.util.ArrayList; + +/** + * DrawersStack is used to show multiple drawers at once + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +@DefaultProperty(value="content") +public class JFXDrawersStack extends StackPane { + + private ArrayList drawers = new ArrayList<>(); + private Node content ; + private Rectangle clip = new Rectangle(); + boolean holding = false; + + /** + * creates empty drawers stack + */ + public JFXDrawersStack() { + super(); + clip.widthProperty().bind(this.widthProperty()); + clip.heightProperty().bind(this.heightProperty()); + this.setClip(clip); + } + + /** + * @return the content of the drawer stack (Note: the parent of the + * content node is that latest active drawer if existed) + */ + public Node getContent(){ + return this.content; + } + + /** + * set the content of the drawers stack, similar to {@link JFXDrawer#setContent(Node...)} + * @param content + */ + public void setContent(Node content){ + this.content = content; + if(drawers.size() > 0) drawers.get(0).setContent(content); + else this.getChildren().add(this.content); + } + + /** + * add JFXDrawer to the stack + * @param drawer + */ + private void addDrawer(JFXDrawer drawer){ + if (drawer == null) return; + + if(drawers.size() == 0){ + if(content!=null) drawer.setContent(content); + this.getChildren().add(drawer); + }else { + drawer.setContent(drawers.get(drawers.size()-1)); + this.getChildren().add(drawer); + } + + drawer.sidePane.addEventHandler(MouseEvent.MOUSE_PRESSED, (event)->{ + holding = true; + new Thread(()->{ + try { + Thread.sleep(300); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + if(holding){ + holding = false; + if(drawers.indexOf(drawer) < drawers.size()-1) + Platform.runLater(()->drawer.bringToFront((param)->{ + updateDrawerPosition(drawer); + return param; + })); + } + }).start(); + }); + + drawer.sidePane.addEventHandler(MouseEvent.MOUSE_DRAGGED, (event)-> holding = false); + drawer.sidePane.addEventHandler(MouseEvent.MOUSE_RELEASED, (event)-> holding = false); + + drawers.add(drawer); + } + + /** + * update drawers position in the stack once a drawer is drawn + * @param drawer + */ + private void updateDrawerPosition(JFXDrawer drawer){ + int index = drawers.indexOf(drawer); + if(index + 1 < drawers.size()){ + if(index - 1 >= 0) drawers.get(index+1).setContent(drawers.get(index-1)); + else if(index == 0) drawers.get(index+1).setContent(content); + } + if(index < drawers.size() - 1){ + drawer.setContent(drawers.get(drawers.size()-1)); + drawers.remove(drawer); + drawers.add(drawer); + this.getChildren().add(drawer); + } + } + + /** + * toggle a drawer in the stack + * + * @param drawer the drawer to be toggled + */ + public void toggle(JFXDrawer drawer){ + if(!drawers.contains(drawer)) addDrawer(drawer); + if(drawer.isShown() || drawer.isShowing()) drawer.close(); + else{ + updateDrawerPosition(drawer); + drawer.open(); + } + } + + /** + * toggle on/off a drawer in the stack + * + * @param drawer the drawer to be toggled + * @param show true to toggle on, false to toggle off + */ + public void toggle(JFXDrawer drawer, boolean show){ + if(!drawers.contains(drawer)) addDrawer(drawer); + if(!show){ + if(drawer.isShown() || drawer.isShowing()) drawer.close(); + }else{ + if(!drawer.isShown() && !drawer.isShowing()){ + updateDrawerPosition(drawer); + drawer.open(); + } + } + } + + +} diff --git a/src/com/jfoenix/controls/JFXHamburger.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXHamburger.java similarity index 96% rename from src/com/jfoenix/controls/JFXHamburger.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXHamburger.java index 55bf9d6f..e2a3de8d 100644 --- a/src/com/jfoenix/controls/JFXHamburger.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/JFXHamburger.java @@ -1,94 +1,94 @@ -/* - * 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 com.jfoenix.transitions.hamburger.HamburgerTransition; -import javafx.animation.Transition; -import javafx.beans.DefaultProperty; -import javafx.geometry.Insets; -import javafx.geometry.Pos; -import javafx.scene.layout.*; -import javafx.scene.paint.Color; - -/** - * the famous animated hamburger icon used in material design - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -@DefaultProperty(value="animation") -public class JFXHamburger extends VBox { - - private Transition animation; - - /** - * creates a hamburger icon - */ - public JFXHamburger() { - - StackPane line1 = new StackPane(); - StackPane line2 = new StackPane(); - StackPane line3 = new StackPane(); - - initStyle(line1); - initStyle(line2); - initStyle(line3); - - this.getChildren().add(line1); - this.getChildren().add(line2); - this.getChildren().add(line3); - - this.getStyleClass().add(DEFAULT_STYLE_CLASS); - this.setAlignment(Pos.CENTER); - this.setFillWidth(false); - this.setSpacing(4); - } - - /** - * @return the current animation of the hamburger - */ - public Transition getAnimation() { - return animation; - } - - /** - * set a specified {@link HamburgerTransition} - * @param animation - */ - public void setAnimation(Transition animation) { - this.animation = ((HamburgerTransition)animation).getAnimation(this); - this.animation.setRate(-1); - } - - private void initStyle(StackPane pane){ - pane.setOpacity(1); - pane.setPrefSize(30, 4); - pane.setBackground(new Background(new BackgroundFill(Color.BLACK, new CornerRadii(5), Insets.EMPTY))); - } - - /** - * Initialize the style class to 'jfx-hamburger'. - * - * This is the selector class from which CSS can be used to style - * this control. - */ - private static final String DEFAULT_STYLE_CLASS = "jfx-hamburger"; - -} +/* + * 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 com.jfoenix.transitions.hamburger.HamburgerTransition; +import javafx.animation.Transition; +import javafx.beans.DefaultProperty; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.layout.*; +import javafx.scene.paint.Color; + +/** + * the famous animated hamburger icon used in material design + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +@DefaultProperty(value="animation") +public class JFXHamburger extends VBox { + + private Transition animation; + + /** + * creates a hamburger icon + */ + public JFXHamburger() { + + StackPane line1 = new StackPane(); + StackPane line2 = new StackPane(); + StackPane line3 = new StackPane(); + + initStyle(line1); + initStyle(line2); + initStyle(line3); + + this.getChildren().add(line1); + this.getChildren().add(line2); + this.getChildren().add(line3); + + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + this.setAlignment(Pos.CENTER); + this.setFillWidth(false); + this.setSpacing(4); + } + + /** + * @return the current animation of the hamburger + */ + public Transition getAnimation() { + return animation; + } + + /** + * set a specified {@link HamburgerTransition} + * @param animation + */ + public void setAnimation(Transition animation) { + this.animation = ((HamburgerTransition)animation).getAnimation(this); + this.animation.setRate(-1); + } + + private void initStyle(StackPane pane){ + pane.setOpacity(1); + pane.setPrefSize(30, 4); + pane.setBackground(new Background(new BackgroundFill(Color.BLACK, new CornerRadii(5), Insets.EMPTY))); + } + + /** + * Initialize the style class to 'jfx-hamburger'. + * + * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-hamburger"; + +} diff --git a/src/com/jfoenix/controls/JFXListCell.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXListCell.java similarity index 97% rename from src/com/jfoenix/controls/JFXListCell.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXListCell.java index 96571968..aa44196c 100644 --- a/src/com/jfoenix/controls/JFXListCell.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/JFXListCell.java @@ -1,416 +1,416 @@ -/* - * 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 com.jfoenix.svg.SVGGlyph; -import javafx.animation.Animation.Status; -import javafx.animation.Interpolator; -import javafx.animation.KeyFrame; -import javafx.animation.KeyValue; -import javafx.animation.Timeline; -import javafx.application.Platform; -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.SimpleBooleanProperty; -import javafx.geometry.Insets; -import javafx.geometry.Pos; -import javafx.scene.Node; -import javafx.scene.control.Control; -import javafx.scene.control.Label; -import javafx.scene.control.ListCell; -import javafx.scene.control.Tooltip; -import javafx.scene.input.MouseEvent; -import javafx.scene.layout.Priority; -import javafx.scene.layout.Region; -import javafx.scene.layout.StackPane; -import javafx.scene.layout.VBox; -import javafx.scene.paint.Color; -import javafx.scene.shape.Rectangle; -import javafx.scene.shape.Shape; -import javafx.util.Duration; - -/** - * material design implementation of ListCell - * - * By default JFXListCell will try to create a graphic node for the cell, - * to override it you need to set graphic to null in {@link #updateItem(Object, boolean)} method. - * - * NOTE: passive nodes (Labels and Shapes) will be set to mouse transparent in order to - * show the ripple effect upon clicking , to change this behavior you can override the - * method {{@link #makeChildrenTransparent()} - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXListCell extends ListCell { - - protected JFXRippler cellRippler = new JFXRippler(new StackPane()){ - @Override protected void initListeners(){ - ripplerPane.setOnMousePressed((event) -> createRipple(event.getX(),event.getY())); - } - }; - - protected Node cellContent; - private Rectangle clip; - // private Timeline animateGap; - private Timeline expandAnimation; - private Timeline gapAnimation; - private double animatedHeight = 0; - private boolean playExpandAnimation = false; - private boolean selectionChanged = false; - /** - * {@inheritDoc} - */ - public JFXListCell() { - super(); - initialize(); - initListeners(); - } - - /** - * init listeners to update the vertical gap / selection animation - */ - private void initListeners(){ - listViewProperty().addListener((listObj,oldList,newList)->{ - if(newList!=null){ - if(getListView() instanceof JFXListView){ - ((JFXListView)newList).currentVerticalGapProperty().addListener((o,oldVal,newVal)->{ - cellRippler.rippler.setClip(null); - if(newVal.doubleValue() != 0) { - playExpandAnimation = true; - getListView().requestLayout(); - }else{ - // fake expand state - double gap = clip.getY() * 2; - gapAnimation = new Timeline( - new KeyFrame(Duration.millis(240), - new KeyValue(this.translateYProperty(), -gap/2 - (gap * (getIndex())), Interpolator.EASE_BOTH) - )); - gapAnimation.play(); - gapAnimation.setOnFinished((finish)->{ - requestLayout(); - Platform.runLater(()-> getListView().requestLayout()); - }); - } - }); - - selectedProperty().addListener((o,oldVal,newVal)->{ - if(newVal) selectionChanged = true; - }); - } - } - }); - } - - /** - * {@inheritDoc} - */ - @Override - protected void layoutChildren() { - super.layoutChildren(); - cellRippler.resizeRelocate(0, 0, getWidth(), getHeight()); - double gap = getGap(); - - if(clip == null){ - clip = new Rectangle(0, gap/2, getWidth(), getHeight()-gap); - setClip(clip); - }else{ - if(gap!=0){ - if(playExpandAnimation || selectionChanged){ - // fake list collapse state - if(playExpandAnimation){ - this.setTranslateY(-gap/2 + (-gap * (getIndex()))); - clip.setY(gap/2); - clip.setHeight(getHeight()-gap); - gapAnimation = new Timeline(new KeyFrame(Duration.millis(240), new KeyValue(this.translateYProperty(), 0, Interpolator.EASE_BOTH))); - playExpandAnimation = false; - } else if(selectionChanged){ - clip.setY(0); - clip.setHeight(getHeight()); - gapAnimation = new Timeline( - new KeyFrame(Duration.millis(240), - new KeyValue(clip.yProperty(), gap/2, Interpolator.EASE_BOTH), - new KeyValue(clip.heightProperty(), getHeight()-gap, Interpolator.EASE_BOTH) - )); - } - playExpandAnimation = false; - selectionChanged = false; - gapAnimation.play(); - }else{ - if(gapAnimation!=null) gapAnimation.stop(); - this.setTranslateY(0); - clip.setY(gap/2); - clip.setHeight(getHeight() - gap); - } - }else{ - this.setTranslateY(0); - clip.setY(0); - clip.setHeight(getHeight()); - } - clip.setX(0); - clip.setWidth(getWidth()); - } - if(!getChildren().contains(cellRippler)){ - makeChildrenTransparent(); - getChildren().add(0,cellRippler); - cellRippler.rippler.clear(); - } - - // refresh sublist style class - if(this.getGraphic()!=null && this.getGraphic().getStyleClass().contains("sublist-container")) this.getStyleClass().add("sublist-item"); - else this.getStyleClass().remove("sublist-item"); - } - - /** - * this method is used to set some nodes in cell content as mouse transparent nodes - * so clicking on them will trigger the ripple effect. - */ - protected void makeChildrenTransparent(){ - for (Node child : getChildren()) - if(child instanceof Label || child instanceof Shape) - child.setMouseTransparent(true); - } - /** - * {@inheritDoc} - */ - @Override - public void updateItem(T item, boolean empty){ - super.updateItem(item,empty); - if(empty){ - setText(null); - setGraphic(null); - // remove empty (Trailing cells) - setMouseTransparent(true); - setStyle("-fx-background-color:TRANSPARENT;"); - - }else{ - if(item != null) { - // if cell is not a trailing cell then show it - setStyle(null); - setMouseTransparent(false); - - Node currentNode = getGraphic(); - - Node newNode; - if((item instanceof Region || item instanceof Control)) newNode = (Node) item; - else newNode = new Label(item.toString()); - - - boolean isJFXListView = getListView() instanceof JFXListView; - - // show cell tooltip if its toggled in JFXListView - if(isJFXListView && ((JFXListView)getListView()).isShowTooltip() && newNode instanceof Label){ - setTooltip(new Tooltip(((Label)newNode).getText())); - } - - - if (currentNode == null || !currentNode.equals(newNode)) { - // clear nodes - cellContent = newNode; - cellRippler.rippler.cacheRippleClip(false); - - // build the Cell node - // RIPPLER ITEM : in case if the list item has its own rippler bind the list rippler and item rippler properties - if(newNode instanceof JFXRippler){ - // build cell container from exisiting rippler - cellRippler.ripplerFillProperty().bind(((JFXRippler)newNode).ripplerFillProperty()); - cellRippler.maskTypeProperty().bind(((JFXRippler)newNode).maskTypeProperty()); - cellRippler.positionProperty().bind(((JFXRippler)newNode).positionProperty()); - cellContent = ((JFXRippler)newNode).getControl(); - } - - // SUBLIST ITEM : build the Cell node as sublist the sublist - else if(newNode instanceof JFXListView){ - // add the sublist to the parent and style the cell as sublist item - ((JFXListView)getListView()).addSublist((JFXListView) newNode, this.getIndex()); - this.getStyleClass().add("sublist-item"); - - if(this.getPadding()!=null) this.setPadding(new Insets(this.getPadding().getTop(),0,this.getPadding().getBottom(),0)); - - // First build the group item used to expand / hide the sublist - StackPane groupNode = new StackPane(); - groupNode.getStyleClass().add("sublist-header"); - SVGGlyph dropIcon = new SVGGlyph(0, "ANGLE_RIGHT", "M340 548.571q0 7.429-5.714 13.143l-266.286 266.286q-5.714 5.714-13.143 5.714t-13.143-5.714l-28.571-28.571q-5.714-5.714-5.714-13.143t5.714-13.143l224.571-224.571-224.571-224.571q-5.714-5.714-5.714-13.143t5.714-13.143l28.571-28.571q5.714-5.714 13.143-5.714t13.143 5.714l266.286 266.286q5.714 5.714 5.714 13.143z", Color.BLACK); - dropIcon.setStyle("-fx-min-width:0.4em;-fx-max-width:0.4em;-fx-min-height:0.6em;-fx-max-height:0.6em;"); - dropIcon.getStyleClass().add("drop-icon"); - /* - * alignment of the group node can be changed using the following css selector - * .jfx-list-view .sublist-header{ } - */ - groupNode.getChildren().setAll(((JFXListView)newNode).getGroupnode(), dropIcon); - // the margin is needed when rotating the angle - StackPane.setMargin(dropIcon, new Insets(0,19,0,0)); - StackPane.setAlignment(dropIcon, Pos.CENTER_RIGHT); - - // Second build the sublist container - StackPane sublistContainer = new StackPane(); - sublistContainer.setMinHeight(0); - sublistContainer.setMaxHeight(0); - sublistContainer.getChildren().setAll(newNode); - sublistContainer.setTranslateY(this.snappedBottomInset()); - sublistContainer.setOpacity(0); - StackPane.setMargin(newNode, new Insets(-1,-1,0,-1)); - - // sublistContainer.heightProperty().addListener((o,oldVal,newVal)->{ - // // store the hieght of the sublist and resize it to 0 to make it hidden - // if(subListHeight == -1){ - // subListHeight = newVal.doubleValue() + this.snappedBottomInset()/2; - // // totalSubListsHeight += subListHeight; - // // set the parent list - // Platform.runLater(()->{ - // sublistContainer.setMinHeight(0); - // sublistContainer.setPrefHeight(0); - // sublistContainer.setMaxHeight(0); - // // double currentHeight = ((JFXListView)getListView()).getHeight(); - // // FIXME : THIS SHOULD ONLY CALLED ONCE ( NOW ITS BEING CALLED FOR EVERY SUBLIST) - // // updateListViewHeight(currentHeight - totalSubListsHeight); - // }); - // } - // }); - // Third, create container of group title and the sublist - VBox contentHolder = new VBox(); - contentHolder.getChildren().setAll(groupNode, sublistContainer); - contentHolder.getStyleClass().add("sublist-container"); - VBox.setVgrow(groupNode, Priority.ALWAYS); - cellContent = contentHolder; - cellRippler.ripplerPane.addEventHandler(MouseEvent.ANY, (e)->e.consume()); - contentHolder.addEventHandler(MouseEvent.ANY, (e)->{ - if(!e.isConsumed()){ - cellRippler.ripplerPane.fireEvent(e); - e.consume(); - } - }); - cellRippler.ripplerPane.addEventHandler(MouseEvent.MOUSE_CLICKED, (e)->{ - if(!e.isConsumed()){ - e.consume(); - contentHolder.fireEvent(e); - } - }); - // cache rippler clip in subnodes - cellRippler.rippler.cacheRippleClip(true); - - this.setOnMouseClicked((e)->e.consume()); - // Finally, add sublist animation - contentHolder.setOnMouseClicked((click)->{ - click.consume(); - // stop the animation or change the list height - if(expandAnimation!=null && expandAnimation.getStatus().equals(Status.RUNNING)) expandAnimation.stop(); - - // invert the expand property - expandedProperty.set(!expandedProperty.get()); - - double newAnimatedHeight = ((Region)newNode).prefHeight(-1) * (expandedProperty.get() ? 1 : -1); - double newHeight = expandedProperty.get() ? this.getHeight() + newAnimatedHeight : this.prefHeight(-1); - // animate showing/hiding the sublist - double contentHeight = expandedProperty.get()? newAnimatedHeight : 0; - - if(expandedProperty.get()){ - updateClipHeight(newHeight); - getListView().setPrefHeight(getListView().getHeight() + newAnimatedHeight + animatedHeight); - } - // update the animated height - animatedHeight = newAnimatedHeight; - - int opacity = expandedProperty.get()? 1 : 0; - expandAnimation = new Timeline(new KeyFrame(Duration.millis(320), - new KeyValue( sublistContainer.minHeightProperty(), contentHeight ,Interpolator.EASE_BOTH), - new KeyValue( sublistContainer.maxHeightProperty(), contentHeight ,Interpolator.EASE_BOTH), - new KeyValue( sublistContainer.opacityProperty(), opacity ,Interpolator.EASE_BOTH))); - - if(!expandedProperty.get()){ - expandAnimation.setOnFinished((finish)->{ - updateClipHeight(newHeight); - getListView().setPrefHeight(getListView().getHeight() + newAnimatedHeight); - animatedHeight = 0; - }); - } - expandAnimation.play(); - }); - - // animate arrow - expandedProperty.addListener((o,oldVal,newVal)->{ - if(newVal) new Timeline(new KeyFrame(Duration.millis(160),new KeyValue( dropIcon.rotateProperty(),90 ,Interpolator.EASE_BOTH))).play(); - else new Timeline(new KeyFrame(Duration.millis(160),new KeyValue( dropIcon.rotateProperty(), 0 ,Interpolator.EASE_BOTH))).play(); - }); - } - - ((Region)cellContent).setMaxHeight(((Region)cellContent).prefHeight(-1)); - setGraphic(cellContent); - setText(null); - } - } - } - } - - - private void updateClipHeight(double newHeight){ - clip.setHeight(newHeight - getGap()); - } - - - /*************************************************************************** - * * - * Properties * - * * - **************************************************************************/ - - // indicate whether the sub list is expanded or not - private BooleanProperty expandedProperty = new SimpleBooleanProperty(false); - - public BooleanProperty expandedProperty(){ - return expandedProperty; - } - public void setExpanded(boolean expand){ - expandedProperty.set(expand); - } - public boolean isExpanded(){ - return expandedProperty.get(); - } - - /*************************************************************************** - * * - * Stylesheet Handling * - * * - **************************************************************************/ - - /** - * Initialize the style class to 'jfx-list-cell'. - * - * This is the selector class from which CSS can be used to style - * this control. - */ - private static final String DEFAULT_STYLE_CLASS = "jfx-list-cell"; - - private void initialize() { - this.getStyleClass().add(DEFAULT_STYLE_CLASS); - // this.setPadding(new Insets(4,8,4,8)); - this.setPadding(new Insets(8,12,8,12)); - // this.setPadding(new Insets(0)); - } - - @Override - protected double computePrefHeight(double width) { - double gap = getGap(); - return super.computePrefHeight(width) + gap; - } - - private double getGap() { - return (getListView() instanceof JFXListView)? (((JFXListView)getListView()).isExpanded()? ((JFXListView)getListView()).currentVerticalGapProperty().get() : 0) : 0; - } -} +/* + * 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 com.jfoenix.svg.SVGGlyph; +import javafx.animation.Animation.Status; +import javafx.animation.Interpolator; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.application.Platform; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Control; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.Tooltip; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import javafx.scene.shape.Rectangle; +import javafx.scene.shape.Shape; +import javafx.util.Duration; + +/** + * material design implementation of ListCell + * + * By default JFXListCell will try to create a graphic node for the cell, + * to override it you need to set graphic to null in {@link #updateItem(Object, boolean)} method. + * + * NOTE: passive nodes (Labels and Shapes) will be set to mouse transparent in order to + * show the ripple effect upon clicking , to change this behavior you can override the + * method {{@link #makeChildrenTransparent()} + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXListCell extends ListCell { + + protected JFXRippler cellRippler = new JFXRippler(new StackPane()){ + @Override protected void initListeners(){ + ripplerPane.setOnMousePressed((event) -> createRipple(event.getX(),event.getY())); + } + }; + + protected Node cellContent; + private Rectangle clip; + // private Timeline animateGap; + private Timeline expandAnimation; + private Timeline gapAnimation; + private double animatedHeight = 0; + private boolean playExpandAnimation = false; + private boolean selectionChanged = false; + /** + * {@inheritDoc} + */ + public JFXListCell() { + super(); + initialize(); + initListeners(); + } + + /** + * init listeners to update the vertical gap / selection animation + */ + private void initListeners(){ + listViewProperty().addListener((listObj,oldList,newList)->{ + if(newList!=null){ + if(getListView() instanceof JFXListView){ + ((JFXListView)newList).currentVerticalGapProperty().addListener((o,oldVal,newVal)->{ + cellRippler.rippler.setClip(null); + if(newVal.doubleValue() != 0) { + playExpandAnimation = true; + getListView().requestLayout(); + }else{ + // fake expand state + double gap = clip.getY() * 2; + gapAnimation = new Timeline( + new KeyFrame(Duration.millis(240), + new KeyValue(this.translateYProperty(), -gap/2 - (gap * (getIndex())), Interpolator.EASE_BOTH) + )); + gapAnimation.play(); + gapAnimation.setOnFinished((finish)->{ + requestLayout(); + Platform.runLater(()-> getListView().requestLayout()); + }); + } + }); + + selectedProperty().addListener((o,oldVal,newVal)->{ + if(newVal) selectionChanged = true; + }); + } + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + protected void layoutChildren() { + super.layoutChildren(); + cellRippler.resizeRelocate(0, 0, getWidth(), getHeight()); + double gap = getGap(); + + if(clip == null){ + clip = new Rectangle(0, gap/2, getWidth(), getHeight()-gap); + setClip(clip); + }else{ + if(gap!=0){ + if(playExpandAnimation || selectionChanged){ + // fake list collapse state + if(playExpandAnimation){ + this.setTranslateY(-gap/2 + (-gap * (getIndex()))); + clip.setY(gap/2); + clip.setHeight(getHeight()-gap); + gapAnimation = new Timeline(new KeyFrame(Duration.millis(240), new KeyValue(this.translateYProperty(), 0, Interpolator.EASE_BOTH))); + playExpandAnimation = false; + } else if(selectionChanged){ + clip.setY(0); + clip.setHeight(getHeight()); + gapAnimation = new Timeline( + new KeyFrame(Duration.millis(240), + new KeyValue(clip.yProperty(), gap/2, Interpolator.EASE_BOTH), + new KeyValue(clip.heightProperty(), getHeight()-gap, Interpolator.EASE_BOTH) + )); + } + playExpandAnimation = false; + selectionChanged = false; + gapAnimation.play(); + }else{ + if(gapAnimation!=null) gapAnimation.stop(); + this.setTranslateY(0); + clip.setY(gap/2); + clip.setHeight(getHeight() - gap); + } + }else{ + this.setTranslateY(0); + clip.setY(0); + clip.setHeight(getHeight()); + } + clip.setX(0); + clip.setWidth(getWidth()); + } + if(!getChildren().contains(cellRippler)){ + makeChildrenTransparent(); + getChildren().add(0,cellRippler); + cellRippler.rippler.clear(); + } + + // refresh sublist style class + if(this.getGraphic()!=null && this.getGraphic().getStyleClass().contains("sublist-container")) this.getStyleClass().add("sublist-item"); + else this.getStyleClass().remove("sublist-item"); + } + + /** + * this method is used to set some nodes in cell content as mouse transparent nodes + * so clicking on them will trigger the ripple effect. + */ + protected void makeChildrenTransparent(){ + for (Node child : getChildren()) + if(child instanceof Label || child instanceof Shape) + child.setMouseTransparent(true); + } + /** + * {@inheritDoc} + */ + @Override + public void updateItem(T item, boolean empty){ + super.updateItem(item,empty); + if(empty){ + setText(null); + setGraphic(null); + // remove empty (Trailing cells) + setMouseTransparent(true); + setStyle("-fx-background-color:TRANSPARENT;"); + + }else{ + if(item != null) { + // if cell is not a trailing cell then show it + setStyle(null); + setMouseTransparent(false); + + Node currentNode = getGraphic(); + + Node newNode; + if((item instanceof Region || item instanceof Control)) newNode = (Node) item; + else newNode = new Label(item.toString()); + + + boolean isJFXListView = getListView() instanceof JFXListView; + + // show cell tooltip if its toggled in JFXListView + if(isJFXListView && ((JFXListView)getListView()).isShowTooltip() && newNode instanceof Label){ + setTooltip(new Tooltip(((Label)newNode).getText())); + } + + + if (currentNode == null || !currentNode.equals(newNode)) { + // clear nodes + cellContent = newNode; + cellRippler.rippler.cacheRippleClip(false); + + // build the Cell node + // RIPPLER ITEM : in case if the list item has its own rippler bind the list rippler and item rippler properties + if(newNode instanceof JFXRippler){ + // build cell container from exisiting rippler + cellRippler.ripplerFillProperty().bind(((JFXRippler)newNode).ripplerFillProperty()); + cellRippler.maskTypeProperty().bind(((JFXRippler)newNode).maskTypeProperty()); + cellRippler.positionProperty().bind(((JFXRippler)newNode).positionProperty()); + cellContent = ((JFXRippler)newNode).getControl(); + } + + // SUBLIST ITEM : build the Cell node as sublist the sublist + else if(newNode instanceof JFXListView){ + // add the sublist to the parent and style the cell as sublist item + ((JFXListView)getListView()).addSublist((JFXListView) newNode, this.getIndex()); + this.getStyleClass().add("sublist-item"); + + if(this.getPadding()!=null) this.setPadding(new Insets(this.getPadding().getTop(),0,this.getPadding().getBottom(),0)); + + // First build the group item used to expand / hide the sublist + StackPane groupNode = new StackPane(); + groupNode.getStyleClass().add("sublist-header"); + SVGGlyph dropIcon = new SVGGlyph(0, "ANGLE_RIGHT", "M340 548.571q0 7.429-5.714 13.143l-266.286 266.286q-5.714 5.714-13.143 5.714t-13.143-5.714l-28.571-28.571q-5.714-5.714-5.714-13.143t5.714-13.143l224.571-224.571-224.571-224.571q-5.714-5.714-5.714-13.143t5.714-13.143l28.571-28.571q5.714-5.714 13.143-5.714t13.143 5.714l266.286 266.286q5.714 5.714 5.714 13.143z", Color.BLACK); + dropIcon.setStyle("-fx-min-width:0.4em;-fx-max-width:0.4em;-fx-min-height:0.6em;-fx-max-height:0.6em;"); + dropIcon.getStyleClass().add("drop-icon"); + /* + * alignment of the group node can be changed using the following css selector + * .jfx-list-view .sublist-header{ } + */ + groupNode.getChildren().setAll(((JFXListView)newNode).getGroupnode(), dropIcon); + // the margin is needed when rotating the angle + StackPane.setMargin(dropIcon, new Insets(0,19,0,0)); + StackPane.setAlignment(dropIcon, Pos.CENTER_RIGHT); + + // Second build the sublist container + StackPane sublistContainer = new StackPane(); + sublistContainer.setMinHeight(0); + sublistContainer.setMaxHeight(0); + sublistContainer.getChildren().setAll(newNode); + sublistContainer.setTranslateY(this.snappedBottomInset()); + sublistContainer.setOpacity(0); + StackPane.setMargin(newNode, new Insets(-1,-1,0,-1)); + + // sublistContainer.heightProperty().addListener((o,oldVal,newVal)->{ + // // store the hieght of the sublist and resize it to 0 to make it hidden + // if(subListHeight == -1){ + // subListHeight = newVal.doubleValue() + this.snappedBottomInset()/2; + // // totalSubListsHeight += subListHeight; + // // set the parent list + // Platform.runLater(()->{ + // sublistContainer.setMinHeight(0); + // sublistContainer.setPrefHeight(0); + // sublistContainer.setMaxHeight(0); + // // double currentHeight = ((JFXListView)getListView()).getHeight(); + // // FIXME : THIS SHOULD ONLY CALLED ONCE ( NOW ITS BEING CALLED FOR EVERY SUBLIST) + // // updateListViewHeight(currentHeight - totalSubListsHeight); + // }); + // } + // }); + // Third, create container of group title and the sublist + VBox contentHolder = new VBox(); + contentHolder.getChildren().setAll(groupNode, sublistContainer); + contentHolder.getStyleClass().add("sublist-container"); + VBox.setVgrow(groupNode, Priority.ALWAYS); + cellContent = contentHolder; + cellRippler.ripplerPane.addEventHandler(MouseEvent.ANY, (e)->e.consume()); + contentHolder.addEventHandler(MouseEvent.ANY, (e)->{ + if(!e.isConsumed()){ + cellRippler.ripplerPane.fireEvent(e); + e.consume(); + } + }); + cellRippler.ripplerPane.addEventHandler(MouseEvent.MOUSE_CLICKED, (e)->{ + if(!e.isConsumed()){ + e.consume(); + contentHolder.fireEvent(e); + } + }); + // cache rippler clip in subnodes + cellRippler.rippler.cacheRippleClip(true); + + this.setOnMouseClicked((e)->e.consume()); + // Finally, add sublist animation + contentHolder.setOnMouseClicked((click)->{ + click.consume(); + // stop the animation or change the list height + if(expandAnimation!=null && expandAnimation.getStatus().equals(Status.RUNNING)) expandAnimation.stop(); + + // invert the expand property + expandedProperty.set(!expandedProperty.get()); + + double newAnimatedHeight = ((Region)newNode).prefHeight(-1) * (expandedProperty.get() ? 1 : -1); + double newHeight = expandedProperty.get() ? this.getHeight() + newAnimatedHeight : this.prefHeight(-1); + // animate showing/hiding the sublist + double contentHeight = expandedProperty.get()? newAnimatedHeight : 0; + + if(expandedProperty.get()){ + updateClipHeight(newHeight); + getListView().setPrefHeight(getListView().getHeight() + newAnimatedHeight + animatedHeight); + } + // update the animated height + animatedHeight = newAnimatedHeight; + + int opacity = expandedProperty.get()? 1 : 0; + expandAnimation = new Timeline(new KeyFrame(Duration.millis(320), + new KeyValue( sublistContainer.minHeightProperty(), contentHeight ,Interpolator.EASE_BOTH), + new KeyValue( sublistContainer.maxHeightProperty(), contentHeight ,Interpolator.EASE_BOTH), + new KeyValue( sublistContainer.opacityProperty(), opacity ,Interpolator.EASE_BOTH))); + + if(!expandedProperty.get()){ + expandAnimation.setOnFinished((finish)->{ + updateClipHeight(newHeight); + getListView().setPrefHeight(getListView().getHeight() + newAnimatedHeight); + animatedHeight = 0; + }); + } + expandAnimation.play(); + }); + + // animate arrow + expandedProperty.addListener((o,oldVal,newVal)->{ + if(newVal) new Timeline(new KeyFrame(Duration.millis(160),new KeyValue( dropIcon.rotateProperty(),90 ,Interpolator.EASE_BOTH))).play(); + else new Timeline(new KeyFrame(Duration.millis(160),new KeyValue( dropIcon.rotateProperty(), 0 ,Interpolator.EASE_BOTH))).play(); + }); + } + + ((Region)cellContent).setMaxHeight(((Region)cellContent).prefHeight(-1)); + setGraphic(cellContent); + setText(null); + } + } + } + } + + + private void updateClipHeight(double newHeight){ + clip.setHeight(newHeight - getGap()); + } + + + /*************************************************************************** + * * + * Properties * + * * + **************************************************************************/ + + // indicate whether the sub list is expanded or not + private BooleanProperty expandedProperty = new SimpleBooleanProperty(false); + + public BooleanProperty expandedProperty(){ + return expandedProperty; + } + public void setExpanded(boolean expand){ + expandedProperty.set(expand); + } + public boolean isExpanded(){ + return expandedProperty.get(); + } + + /*************************************************************************** + * * + * Stylesheet Handling * + * * + **************************************************************************/ + + /** + * Initialize the style class to 'jfx-list-cell'. + * + * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-list-cell"; + + private void initialize() { + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + // this.setPadding(new Insets(4,8,4,8)); + this.setPadding(new Insets(8,12,8,12)); + // this.setPadding(new Insets(0)); + } + + @Override + protected double computePrefHeight(double width) { + double gap = getGap(); + return super.computePrefHeight(width) + gap; + } + + private double getGap() { + return (getListView() instanceof JFXListView)? (((JFXListView)getListView()).isExpanded()? ((JFXListView)getListView()).currentVerticalGapProperty().get() : 0) : 0; + } +} diff --git a/src/com/jfoenix/controls/JFXListView.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXListView.java similarity index 97% rename from src/com/jfoenix/controls/JFXListView.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXListView.java index a7dc7515..7d425dd7 100644 --- a/src/com/jfoenix/controls/JFXListView.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/JFXListView.java @@ -1,400 +1,400 @@ -/* - * 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 com.jfoenix.skins.JFXListViewSkin; -import com.sun.javafx.css.converters.BooleanConverter; -import com.sun.javafx.css.converters.SizeConverter; -import javafx.beans.property.*; -import javafx.collections.FXCollections; -import javafx.collections.ListChangeListener; -import javafx.collections.ObservableList; -import javafx.css.*; -import javafx.event.Event; -import javafx.scene.Node; -import javafx.scene.control.*; -import javafx.scene.input.ContextMenuEvent; -import javafx.scene.input.MouseEvent; -import javafx.util.Callback; - -import java.util.*; - -/** - * Material design implementation of List View - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXListView extends ListView { - - /** - * {@inheritDoc} - */ - public JFXListView() { - super(); - this.setCellFactory(new Callback, ListCell>() { - @Override - public ListCell call(ListView listView) { - return new JFXListCell(); - } - }); - initialize(); - } - - /** - * {@inheritDoc} - */ - @Override - protected Skin createDefaultSkin() { - return new JFXListViewSkin(this); - } - - private ObjectProperty depthProperty = new SimpleObjectProperty(0); - public ObjectProperty depthProperty(){ - return depthProperty; - } - public int getDepth(){ - return depthProperty.get(); - } - public void setDepth(int depth){ - depthProperty.set(depth); - } - - private ReadOnlyDoubleWrapper currentVerticalGapProperty = new ReadOnlyDoubleWrapper(); - - ReadOnlyDoubleProperty currentVerticalGapProperty(){ - return currentVerticalGapProperty.getReadOnlyProperty(); - } - - private void expand(){ - currentVerticalGapProperty.set(verticalGap.get()); - } - - private void collapse(){ - currentVerticalGapProperty.set(0); - } - - /* - * this only works if the items were labels / strings - */ - private BooleanProperty showTooltip = new SimpleBooleanProperty(false); - - public final BooleanProperty showTooltipProperty() { - return this.showTooltip; - } - - public final boolean isShowTooltip() { - return this.showTooltipProperty().get(); - } - - public final void setShowTooltip(final boolean showTooltip) { - this.showTooltipProperty().set(showTooltip); - } - - /*************************************************************************** - * * - * SubList Properties * - * * - **************************************************************************/ - - private ObjectProperty groupnode = new SimpleObjectProperty(new Label("GROUP")); - - public Node getGroupnode(){ - return groupnode.get(); - } - public void setGroupnode(Node node){ - this.groupnode.set(node); - } - - /* - * selected index property that includes the sublists - */ - private ReadOnlyObjectWrapper overAllIndexProperty = new ReadOnlyObjectWrapper(-1); - - public ReadOnlyObjectProperty overAllIndexProperty(){ - return overAllIndexProperty.getReadOnlyProperty(); - } - - // private sublists property - private ObjectProperty>> sublistsProperty = new SimpleObjectProperty>>(FXCollections.observableArrayList()); - private LinkedHashMap> sublistsIndices = new LinkedHashMap>(); - - // this method shouldn't be called from user - void addSublist(JFXListView subList, int index){ - if(!sublistsProperty.get().contains(subList)){ - sublistsProperty.get().add(subList); - sublistsIndices.put(index, subList); - subList.getSelectionModel().selectedIndexProperty().addListener((o,oldVal,newVal)->{ - if(newVal.intValue() != -1){ - udpateOverAllSelectedIndex(); - } - }); - } - } - - private void udpateOverAllSelectedIndex(){ - // if item from the list is selected - if(this.getSelectionModel().getSelectedIndex() != -1 ){ - int selectedIndex = this.getSelectionModel().getSelectedIndex(); - Iterator>> itr = sublistsIndices.entrySet().iterator(); - int preItemsSize = 0; - while(itr.hasNext()){ - Map.Entry> entry = itr.next(); - if(entry.getKey() < selectedIndex) preItemsSize += entry.getValue().getItems().size()-1; - } -// int preItemsSize = sublistsIndices.keySet().stream().filter(key-> key < selectedIndex).mapToInt(key->sublistsIndices.get(key).getItems().size()-1).sum(); - overAllIndexProperty.set(selectedIndex + preItemsSize); - }else{ - Iterator>> itr = sublistsIndices.entrySet().iterator(); - ArrayList selectedList = new ArrayList<>(); - while(itr.hasNext()){ - Map.Entry> entry = itr.next(); - if(entry.getValue().getSelectionModel().getSelectedIndex() != -1){ - selectedList.add(entry.getKey()); - } - } - if(selectedList.size() > 0){ - itr = sublistsIndices.entrySet().iterator(); - int preItemsSize = 0; - while(itr.hasNext()){ - Map.Entry> entry = itr.next(); - if(entry.getKey() < ((Integer)selectedList.get(0))){ - preItemsSize += entry.getValue().getItems().size()-1; - } - } - overAllIndexProperty.set(preItemsSize + (Integer)selectedList.get(0)+ sublistsIndices.get(selectedList.get(0)).getSelectionModel().getSelectedIndex()); - }else{ - overAllIndexProperty.set(-1); - } -// Object[] selectedList = sublistsIndices.keySet().stream().filter(key-> sublistsIndices.get(key).getSelectionModel().getSelectedIndex() != -1).toArray(); -// if(selectedList.length > 0){ -// int preItemsSize = sublistsIndices.keySet().stream().filter(key-> key < ((Integer)selectedList[0])).mapToInt(key-> sublistsIndices.get(key).getItems().size()-1).sum(); -// overAllIndexProperty.set(preItemsSize + (Integer)selectedList[0] + sublistsIndices.get(selectedList[0]).getSelectionModel().getSelectedIndex()); -// }else{ -// overAllIndexProperty.set(-1); -// } - } - } - - - /*************************************************************************** - * * - * Stylesheet Handling * - * * - **************************************************************************/ - - /** - * Initialize the style class to 'jfx-list-view'. - * - * This is the selector class from which CSS can be used to style - * this control. - */ - private static final String DEFAULT_STYLE_CLASS = "jfx-list-view"; - - private void initialize() { - this.getStyleClass().add(DEFAULT_STYLE_CLASS); - expanded.addListener((o,oldVal,newVal)->{ - if(newVal) expand(); - else collapse(); - }); - - verticalGap.addListener((o,oldVal,newVal)->{ - if(isExpanded()) expand(); - else collapse(); - }); - - // handle selection model on the list ( FOR NOW : we only support single selection on the list if it contains sublists) - sublistsProperty.get().addListener( (ListChangeListener.Change> c)->{ - while (c.next()) { - if(c.wasAdded() || c.wasUpdated() || c.wasReplaced()){ - if( sublistsProperty.get().size() == 1) { - this.getSelectionModel().selectedItemProperty().addListener((o,oldVal,newVal)->clearSelection(this)); - // prevent selecting the sublist item by clicking the right mouse button - this.addEventFilter(ContextMenuEvent.CONTEXT_MENU_REQUESTED, Event::consume); - } - c.getAddedSubList().forEach(item -> item.getSelectionModel().selectedItemProperty().addListener((o,oldVal,newVal)->clearSelection(item))); - } - } - }); - - // listen to index changes - this.getSelectionModel().selectedIndexProperty().addListener((o,oldVal,newVal)->{ - if(newVal.intValue() != -1){ - udpateOverAllSelectedIndex(); - } - }); - } - - - // allow single selection across the list and all sublits - private boolean allowClear = true; - private void clearSelection(JFXListView selectedList){ - if(allowClear){ - allowClear = false; - if(this != selectedList) this.getSelectionModel().clearSelection(); - for(int i =0 ; i < sublistsProperty.get().size();i++) - if(sublistsProperty.get().get(i) != selectedList) - sublistsProperty.get().get(i).getSelectionModel().clearSelection(); -// sublistsProperty.get().stream().filter(list-> list!=selectedList).forEach(list->list.getSelectionModel().clearSelection()); - allowClear = true; - } - } - - /** - * propagate mouse events to the parent node ( e.g. to allow dragging while clicking on the list) - */ - public void propagateMouseEventsToParent(){ - this.addEventHandler(MouseEvent.ANY, (e)->{ - e.consume(); - this.getParent().fireEvent(e); - }); - } - - private StyleableDoubleProperty cellHorizontalMargin = new SimpleStyleableDoubleProperty(StyleableProperties.CELL_HORIZONTAL_MARGIN, JFXListView.this, "cellHorizontalMargin", 0.0); - - public Double getCellHorizontalMargin(){ - return cellHorizontalMargin == null ? 0 : cellHorizontalMargin.get(); - } - public StyleableDoubleProperty cellHorizontalMarginProperty(){ - return this.cellHorizontalMargin; - } - public void setCellHorizontalMargin(Double margin){ - this.cellHorizontalMargin.set(margin); - } - - private StyleableDoubleProperty cellVerticalMargin = new SimpleStyleableDoubleProperty(StyleableProperties.CELL_VERTICAL_MARGIN, JFXListView.this, "cellVerticalMargin", 4.0 ); - - public Double getCellVerticalMargin(){ - return cellVerticalMargin == null ? 4 : cellVerticalMargin.get(); - } - public StyleableDoubleProperty cellVerticalMarginProperty(){ - return this.cellVerticalMargin; - } - public void setCellVerticalMargin(Double margin){ - this.cellVerticalMargin.set(margin); - } - - private StyleableDoubleProperty verticalGap = new SimpleStyleableDoubleProperty(StyleableProperties.VERTICAL_GAP, JFXListView.this, "verticalGap", 0.0 ); - - public Double getVerticalGap(){ - return verticalGap == null ? 0 : verticalGap.get(); - } - public StyleableDoubleProperty verticalGapProperty(){ - return this.verticalGap; - } - public void setVerticalGap(Double gap){ - this.verticalGap.set(gap); - } - - private StyleableBooleanProperty expanded = new SimpleStyleableBooleanProperty(StyleableProperties.EXPANDED, JFXListView.this, "expanded", false ); - - public Boolean isExpanded(){ - return expanded == null ? false : expanded.get(); - } - public StyleableBooleanProperty expandedProperty(){ - return this.expanded; - } - public void setExpanded(Boolean expanded){ - this.expanded.set(expanded); - } - - private static class StyleableProperties { - private static final CssMetaData< JFXListView, Number> CELL_HORIZONTAL_MARGIN = - new CssMetaData< JFXListView, Number>("-jfx-cell-horizontal-margin", - SizeConverter.getInstance(), 0) { - @Override - public boolean isSettable(JFXListView control) { - return control.cellHorizontalMargin == null || !control.cellHorizontalMargin.isBound(); - } - @Override - public StyleableDoubleProperty getStyleableProperty(JFXListView control) { - return control.cellHorizontalMarginProperty(); - } - }; - private static final CssMetaData< JFXListView, Number> CELL_VERTICAL_MARGIN = - new CssMetaData< JFXListView, Number>("-jfx-cell-vertical-margin", - SizeConverter.getInstance(), 4) { - @Override - public boolean isSettable(JFXListView control) { - return control.cellVerticalMargin == null || !control.cellVerticalMargin.isBound(); - } - @Override - public StyleableDoubleProperty getStyleableProperty(JFXListView control) { - return control.cellVerticalMarginProperty(); - } - }; - private static final CssMetaData< JFXListView, Number> VERTICAL_GAP = - new CssMetaData< JFXListView, Number>("-jfx-vertical-gap", - SizeConverter.getInstance(), 0) { - @Override - public boolean isSettable(JFXListView control) { - return control.verticalGap == null || !control.verticalGap.isBound(); - } - @Override - public StyleableDoubleProperty getStyleableProperty(JFXListView control) { - return control.verticalGapProperty(); - } - }; - private static final CssMetaData< JFXListView, Boolean> EXPANDED = - new CssMetaData< JFXListView, Boolean>("-jfx-expanded", - BooleanConverter.getInstance(), false) { - @Override - public boolean isSettable(JFXListView control) { - // it's only settable if the List is not shown yet - return control.getHeight() == 0 && ( control.expanded == null || !control.expanded.isBound() ); - } - @Override - public StyleableBooleanProperty getStyleableProperty(JFXListView control) { - return control.expandedProperty(); - } - }; - private static final List> CHILD_STYLEABLES; - static { - final List> styleables = - new ArrayList>(Control.getClassCssMetaData()); - Collections.addAll(styleables, - CELL_HORIZONTAL_MARGIN, - CELL_VERTICAL_MARGIN, - VERTICAL_GAP, - EXPANDED - ); - CHILD_STYLEABLES = Collections.unmodifiableList(styleables); - } - } - - // inherit the styleable properties from parent - private List> STYLEABLES; - - @Override - public List> getControlCssMetaData() { - if(STYLEABLES == null){ - final List> styleables = - new ArrayList>(Control.getClassCssMetaData()); - styleables.addAll(getClassCssMetaData()); - styleables.addAll(super.getClassCssMetaData()); - STYLEABLES = Collections.unmodifiableList(styleables); - } - return STYLEABLES; - } - public static List> getClassCssMetaData() { - return StyleableProperties.CHILD_STYLEABLES; - } - -} +/* + * 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 com.jfoenix.skins.JFXListViewSkin; +import com.sun.javafx.css.converters.BooleanConverter; +import com.sun.javafx.css.converters.SizeConverter; +import javafx.beans.property.*; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.css.*; +import javafx.event.Event; +import javafx.scene.Node; +import javafx.scene.control.*; +import javafx.scene.input.ContextMenuEvent; +import javafx.scene.input.MouseEvent; +import javafx.util.Callback; + +import java.util.*; + +/** + * Material design implementation of List View + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXListView extends ListView { + + /** + * {@inheritDoc} + */ + public JFXListView() { + super(); + this.setCellFactory(new Callback, ListCell>() { + @Override + public ListCell call(ListView listView) { + return new JFXListCell(); + } + }); + initialize(); + } + + /** + * {@inheritDoc} + */ + @Override + protected Skin createDefaultSkin() { + return new JFXListViewSkin(this); + } + + private ObjectProperty depthProperty = new SimpleObjectProperty(0); + public ObjectProperty depthProperty(){ + return depthProperty; + } + public int getDepth(){ + return depthProperty.get(); + } + public void setDepth(int depth){ + depthProperty.set(depth); + } + + private ReadOnlyDoubleWrapper currentVerticalGapProperty = new ReadOnlyDoubleWrapper(); + + ReadOnlyDoubleProperty currentVerticalGapProperty(){ + return currentVerticalGapProperty.getReadOnlyProperty(); + } + + private void expand(){ + currentVerticalGapProperty.set(verticalGap.get()); + } + + private void collapse(){ + currentVerticalGapProperty.set(0); + } + + /* + * this only works if the items were labels / strings + */ + private BooleanProperty showTooltip = new SimpleBooleanProperty(false); + + public final BooleanProperty showTooltipProperty() { + return this.showTooltip; + } + + public final boolean isShowTooltip() { + return this.showTooltipProperty().get(); + } + + public final void setShowTooltip(final boolean showTooltip) { + this.showTooltipProperty().set(showTooltip); + } + + /*************************************************************************** + * * + * SubList Properties * + * * + **************************************************************************/ + + private ObjectProperty groupnode = new SimpleObjectProperty(new Label("GROUP")); + + public Node getGroupnode(){ + return groupnode.get(); + } + public void setGroupnode(Node node){ + this.groupnode.set(node); + } + + /* + * selected index property that includes the sublists + */ + private ReadOnlyObjectWrapper overAllIndexProperty = new ReadOnlyObjectWrapper(-1); + + public ReadOnlyObjectProperty overAllIndexProperty(){ + return overAllIndexProperty.getReadOnlyProperty(); + } + + // private sublists property + private ObjectProperty>> sublistsProperty = new SimpleObjectProperty>>(FXCollections.observableArrayList()); + private LinkedHashMap> sublistsIndices = new LinkedHashMap>(); + + // this method shouldn't be called from user + void addSublist(JFXListView subList, int index){ + if(!sublistsProperty.get().contains(subList)){ + sublistsProperty.get().add(subList); + sublistsIndices.put(index, subList); + subList.getSelectionModel().selectedIndexProperty().addListener((o,oldVal,newVal)->{ + if(newVal.intValue() != -1){ + udpateOverAllSelectedIndex(); + } + }); + } + } + + private void udpateOverAllSelectedIndex(){ + // if item from the list is selected + if(this.getSelectionModel().getSelectedIndex() != -1 ){ + int selectedIndex = this.getSelectionModel().getSelectedIndex(); + Iterator>> itr = sublistsIndices.entrySet().iterator(); + int preItemsSize = 0; + while(itr.hasNext()){ + Map.Entry> entry = itr.next(); + if(entry.getKey() < selectedIndex) preItemsSize += entry.getValue().getItems().size()-1; + } +// int preItemsSize = sublistsIndices.keySet().stream().filter(key-> key < selectedIndex).mapToInt(key->sublistsIndices.get(key).getItems().size()-1).sum(); + overAllIndexProperty.set(selectedIndex + preItemsSize); + }else{ + Iterator>> itr = sublistsIndices.entrySet().iterator(); + ArrayList selectedList = new ArrayList<>(); + while(itr.hasNext()){ + Map.Entry> entry = itr.next(); + if(entry.getValue().getSelectionModel().getSelectedIndex() != -1){ + selectedList.add(entry.getKey()); + } + } + if(selectedList.size() > 0){ + itr = sublistsIndices.entrySet().iterator(); + int preItemsSize = 0; + while(itr.hasNext()){ + Map.Entry> entry = itr.next(); + if(entry.getKey() < ((Integer)selectedList.get(0))){ + preItemsSize += entry.getValue().getItems().size()-1; + } + } + overAllIndexProperty.set(preItemsSize + (Integer)selectedList.get(0)+ sublistsIndices.get(selectedList.get(0)).getSelectionModel().getSelectedIndex()); + }else{ + overAllIndexProperty.set(-1); + } +// Object[] selectedList = sublistsIndices.keySet().stream().filter(key-> sublistsIndices.get(key).getSelectionModel().getSelectedIndex() != -1).toArray(); +// if(selectedList.length > 0){ +// int preItemsSize = sublistsIndices.keySet().stream().filter(key-> key < ((Integer)selectedList[0])).mapToInt(key-> sublistsIndices.get(key).getItems().size()-1).sum(); +// overAllIndexProperty.set(preItemsSize + (Integer)selectedList[0] + sublistsIndices.get(selectedList[0]).getSelectionModel().getSelectedIndex()); +// }else{ +// overAllIndexProperty.set(-1); +// } + } + } + + + /*************************************************************************** + * * + * Stylesheet Handling * + * * + **************************************************************************/ + + /** + * Initialize the style class to 'jfx-list-view'. + * + * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-list-view"; + + private void initialize() { + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + expanded.addListener((o,oldVal,newVal)->{ + if(newVal) expand(); + else collapse(); + }); + + verticalGap.addListener((o,oldVal,newVal)->{ + if(isExpanded()) expand(); + else collapse(); + }); + + // handle selection model on the list ( FOR NOW : we only support single selection on the list if it contains sublists) + sublistsProperty.get().addListener( (ListChangeListener.Change> c)->{ + while (c.next()) { + if(c.wasAdded() || c.wasUpdated() || c.wasReplaced()){ + if( sublistsProperty.get().size() == 1) { + this.getSelectionModel().selectedItemProperty().addListener((o,oldVal,newVal)->clearSelection(this)); + // prevent selecting the sublist item by clicking the right mouse button + this.addEventFilter(ContextMenuEvent.CONTEXT_MENU_REQUESTED, Event::consume); + } + c.getAddedSubList().forEach(item -> item.getSelectionModel().selectedItemProperty().addListener((o,oldVal,newVal)->clearSelection(item))); + } + } + }); + + // listen to index changes + this.getSelectionModel().selectedIndexProperty().addListener((o,oldVal,newVal)->{ + if(newVal.intValue() != -1){ + udpateOverAllSelectedIndex(); + } + }); + } + + + // allow single selection across the list and all sublits + private boolean allowClear = true; + private void clearSelection(JFXListView selectedList){ + if(allowClear){ + allowClear = false; + if(this != selectedList) this.getSelectionModel().clearSelection(); + for(int i =0 ; i < sublistsProperty.get().size();i++) + if(sublistsProperty.get().get(i) != selectedList) + sublistsProperty.get().get(i).getSelectionModel().clearSelection(); +// sublistsProperty.get().stream().filter(list-> list!=selectedList).forEach(list->list.getSelectionModel().clearSelection()); + allowClear = true; + } + } + + /** + * propagate mouse events to the parent node ( e.g. to allow dragging while clicking on the list) + */ + public void propagateMouseEventsToParent(){ + this.addEventHandler(MouseEvent.ANY, (e)->{ + e.consume(); + this.getParent().fireEvent(e); + }); + } + + private StyleableDoubleProperty cellHorizontalMargin = new SimpleStyleableDoubleProperty(StyleableProperties.CELL_HORIZONTAL_MARGIN, JFXListView.this, "cellHorizontalMargin", 0.0); + + public Double getCellHorizontalMargin(){ + return cellHorizontalMargin == null ? 0 : cellHorizontalMargin.get(); + } + public StyleableDoubleProperty cellHorizontalMarginProperty(){ + return this.cellHorizontalMargin; + } + public void setCellHorizontalMargin(Double margin){ + this.cellHorizontalMargin.set(margin); + } + + private StyleableDoubleProperty cellVerticalMargin = new SimpleStyleableDoubleProperty(StyleableProperties.CELL_VERTICAL_MARGIN, JFXListView.this, "cellVerticalMargin", 4.0 ); + + public Double getCellVerticalMargin(){ + return cellVerticalMargin == null ? 4 : cellVerticalMargin.get(); + } + public StyleableDoubleProperty cellVerticalMarginProperty(){ + return this.cellVerticalMargin; + } + public void setCellVerticalMargin(Double margin){ + this.cellVerticalMargin.set(margin); + } + + private StyleableDoubleProperty verticalGap = new SimpleStyleableDoubleProperty(StyleableProperties.VERTICAL_GAP, JFXListView.this, "verticalGap", 0.0 ); + + public Double getVerticalGap(){ + return verticalGap == null ? 0 : verticalGap.get(); + } + public StyleableDoubleProperty verticalGapProperty(){ + return this.verticalGap; + } + public void setVerticalGap(Double gap){ + this.verticalGap.set(gap); + } + + private StyleableBooleanProperty expanded = new SimpleStyleableBooleanProperty(StyleableProperties.EXPANDED, JFXListView.this, "expanded", false ); + + public Boolean isExpanded(){ + return expanded == null ? false : expanded.get(); + } + public StyleableBooleanProperty expandedProperty(){ + return this.expanded; + } + public void setExpanded(Boolean expanded){ + this.expanded.set(expanded); + } + + private static class StyleableProperties { + private static final CssMetaData< JFXListView, Number> CELL_HORIZONTAL_MARGIN = + new CssMetaData< JFXListView, Number>("-jfx-cell-horizontal-margin", + SizeConverter.getInstance(), 0) { + @Override + public boolean isSettable(JFXListView control) { + return control.cellHorizontalMargin == null || !control.cellHorizontalMargin.isBound(); + } + @Override + public StyleableDoubleProperty getStyleableProperty(JFXListView control) { + return control.cellHorizontalMarginProperty(); + } + }; + private static final CssMetaData< JFXListView, Number> CELL_VERTICAL_MARGIN = + new CssMetaData< JFXListView, Number>("-jfx-cell-vertical-margin", + SizeConverter.getInstance(), 4) { + @Override + public boolean isSettable(JFXListView control) { + return control.cellVerticalMargin == null || !control.cellVerticalMargin.isBound(); + } + @Override + public StyleableDoubleProperty getStyleableProperty(JFXListView control) { + return control.cellVerticalMarginProperty(); + } + }; + private static final CssMetaData< JFXListView, Number> VERTICAL_GAP = + new CssMetaData< JFXListView, Number>("-jfx-vertical-gap", + SizeConverter.getInstance(), 0) { + @Override + public boolean isSettable(JFXListView control) { + return control.verticalGap == null || !control.verticalGap.isBound(); + } + @Override + public StyleableDoubleProperty getStyleableProperty(JFXListView control) { + return control.verticalGapProperty(); + } + }; + private static final CssMetaData< JFXListView, Boolean> EXPANDED = + new CssMetaData< JFXListView, Boolean>("-jfx-expanded", + BooleanConverter.getInstance(), false) { + @Override + public boolean isSettable(JFXListView control) { + // it's only settable if the List is not shown yet + return control.getHeight() == 0 && ( control.expanded == null || !control.expanded.isBound() ); + } + @Override + public StyleableBooleanProperty getStyleableProperty(JFXListView control) { + return control.expandedProperty(); + } + }; + private static final List> CHILD_STYLEABLES; + static { + final List> styleables = + new ArrayList>(Control.getClassCssMetaData()); + Collections.addAll(styleables, + CELL_HORIZONTAL_MARGIN, + CELL_VERTICAL_MARGIN, + VERTICAL_GAP, + EXPANDED + ); + CHILD_STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + // inherit the styleable properties from parent + private List> STYLEABLES; + + @Override + public List> getControlCssMetaData() { + if(STYLEABLES == null){ + final List> styleables = + new ArrayList>(Control.getClassCssMetaData()); + styleables.addAll(getClassCssMetaData()); + styleables.addAll(super.getClassCssMetaData()); + STYLEABLES = Collections.unmodifiableList(styleables); + } + return STYLEABLES; + } + public static List> getClassCssMetaData() { + return StyleableProperties.CHILD_STYLEABLES; + } + +} diff --git a/src/com/jfoenix/controls/JFXMasonryPane.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXMasonryPane.java similarity index 97% rename from src/com/jfoenix/controls/JFXMasonryPane.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXMasonryPane.java index 52ae90df..da385c17 100644 --- a/src/com/jfoenix/controls/JFXMasonryPane.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/JFXMasonryPane.java @@ -1,610 +1,610 @@ -/* - * 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 com.jfoenix.transitions.CachedTransition; -import javafx.animation.*; -import javafx.beans.property.*; -import javafx.beans.value.ChangeListener; -import javafx.collections.ListChangeListener.Change; -import javafx.geometry.BoundingBox; -import javafx.scene.Node; -import javafx.scene.layout.GridPane; -import javafx.scene.layout.Pane; -import javafx.scene.layout.Region; -import javafx.util.Duration; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -/** - * A JFXMasonryPane implements asymmetrical grid layoutMode, it places the child nodes according to - * one of the modes: - * - *

Masonry Layout

Nodes will be added one after another, first in the horizontal direction - * , then vertically. sort of like a mason fitting stones in a wall. - * - *

Bin Packing Layout(First Fit)

it works similar to masonry layoutMode, however it tries to - * fill the empty gaps caused in masonry layoutMode. - * - * Note: childs that doesn't fit in the grid will be hidden. - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-05-24 - * - */ -public class JFXMasonryPane extends Pane { - - /*************************************************************************** - * * - * Private Fields * - * * - **************************************************************************/ - private boolean performingLayout = false; - // these variables are computed when layoutChildren is called - private int[][] matrix; - private HashMap animationMap = null; - private ParallelTransition trans = new ParallelTransition(); - private boolean valid = false; -// private GridPane root = new GridPane(); -// String[] colors = {"-fx-border-color:#EEEEEE", "-fx-border-color:RED;","-fx-border-color:BLUE; ","-fx-border-color:GREEN;", "-fx-border-color:PURPLE;"}; - private List oldBoxes; - - /*************************************************************************** - * * - * Constructor * - * * - **************************************************************************/ - /** - * Constructs a new JFXMasonryPane - */ - public JFXMasonryPane() { - this.widthProperty().addListener((o,oldVal,newVal)->{valid = false;}); - this.heightProperty().addListener((o,oldVal,newVal)->{valid = false;}); - ChangeListener layoutListener = (o,oldVal,newVal)->{ - valid = false; - this.requestLayout(); - }; - this.cellWidthProperty().addListener(layoutListener); - this.cellHeightProperty().addListener(layoutListener); - this.hSpacingProperty().addListener(layoutListener); - this.vSpacingProperty().addListener(layoutListener); - this.limitColumnProperty().addListener(layoutListener); - this.limitRowProperty().addListener(layoutListener); - this.getChildren().addListener((Change c)->{ - valid = false; - matrix = null; - this.requestLayout(); - }); - } - /*************************************************************************** - * * - * Override/Inherited methods * - * * - **************************************************************************/ - /** - * {@inheritDoc} - */ - @Override - protected void layoutChildren() { - performingLayout = true; - if(!valid){ - - int col, row; - col = (int) Math.floor(this.getWidth()/ (getCellWidth() + 2*getHSpacing())); - col = getLimitColumn() != -1 && col > getLimitColumn()? getLimitColumn() : col; - - if(matrix!=null && col == matrix[0].length) { - performingLayout = false; - return; - } - //(int) Math.floor(this.getHeight() / (cellH + 2*vSpacing)); - row = 100; - row = getLimitRow() != -1 && row > getLimitRow() ? getLimitRow() : row; - - matrix = new int[row][col]; - double minWidth = -1; - double minHeight = -1; - - List newBoxes; - List childs = new ArrayList<>(); - for(int i = 0 ; i < getChildren().size(); i++) - if(getChildren().get(i) instanceof Region) childs.add((Region) getChildren().get(i)); - newBoxes = layoutMode.get().fillGrid(matrix, childs, getCellWidth() , getCellHeight() , row, col, getHSpacing(), getVSpacing()); - - if(newBoxes == null){ - performingLayout = false; - return; - } - for (int i = 0; i < getChildren().size() && i < newBoxes.size(); i++) { - Region block = (Region) getChildren().get(i); - if(!(block instanceof GridPane)){ - double blockX, blockY, blockWidth, blockHeight; - if(newBoxes.get(i)!=null){ - blockX = newBoxes.get(i).getMinY()*getCellWidth() + ((newBoxes.get(i).getMinY()+1)*2-1)*getHSpacing(); - blockY = newBoxes.get(i).getMinX()* getCellHeight() + ((newBoxes.get(i).getMinX()+1)*2-1)*getVSpacing(); - blockWidth = newBoxes.get(i).getWidth()*getCellWidth() + (newBoxes.get(i).getWidth()-1)*2*getHSpacing(); - blockHeight = newBoxes.get(i).getHeight()* getCellHeight() + (newBoxes.get(i).getHeight()-1)*2*getVSpacing(); - }else{ - blockX = block.getLayoutX(); - blockY = block.getLayoutY(); - blockWidth = -1; - blockHeight = -1; - } - - if(animationMap == null){ - // init static children - block.setLayoutX(blockX); - block.setLayoutY(blockY); - block.setPrefSize(blockWidth, blockHeight); - block.resizeRelocate(blockX, blockY , blockWidth, blockHeight); - }else{ - if(oldBoxes == null || i >= oldBoxes.size()){ - // handle new children - block.setOpacity(0); - block.setLayoutX(blockX); - block.setLayoutY(blockY); - block.setPrefSize(blockWidth, blockHeight); - block.resizeRelocate(blockX, blockY , blockWidth, blockHeight); - } - - if(newBoxes.get(i)!=null){ - // handle children repositioning - animationMap.put(block, new CachedTransition(block, new Timeline(new KeyFrame(Duration.millis(2000), - new KeyValue(block.opacityProperty(), 1, Interpolator.LINEAR), - new KeyValue(block.layoutXProperty(), blockX, Interpolator.LINEAR), - new KeyValue(block.layoutYProperty(), blockY , Interpolator.LINEAR)))){{ - setCycleDuration(Duration.seconds(0.320)); - setDelay(Duration.seconds(0)); - setOnFinished((finish)->{ - block.setLayoutX(blockX); - block.setLayoutY(blockY); - block.setOpacity(1); - }); - }}); - - } else { - // handle children is being hidden ( cause it can't fit in the pane ) - animationMap.put(block, new CachedTransition(block, new Timeline(new KeyFrame(Duration.millis(2000), - new KeyValue(block.opacityProperty(), 0, Interpolator.LINEAR), - new KeyValue(block.layoutXProperty(), blockX, Interpolator.LINEAR), - new KeyValue(block.layoutYProperty(), blockY , Interpolator.LINEAR)))){{ - setCycleDuration(Duration.seconds(0.320)); - setDelay(Duration.seconds(0)); - setOnFinished((finish)->{ - block.setLayoutX(blockX); - block.setLayoutY(blockY); - block.setOpacity(0); - }); - }}); - } - - } - if(newBoxes.get(i)!=null){ - if(blockX + blockWidth> minWidth ) minWidth = blockX + blockWidth; - if(blockY + blockHeight > minHeight ) minHeight = blockY + blockHeight; - } - } - } - this.setMinSize(minWidth , minHeight); - if(animationMap == null) animationMap = new HashMap<>(); - - trans.stop(); - ParallelTransition newTransition = new ParallelTransition(); - newTransition.getChildren().addAll(animationMap.values()); - newTransition.play(); - trans = newTransition; - oldBoxes = newBoxes; - - // FOR DEGBBUGING - -// root.getChildren().clear(); -// for(int y = 0; y < matrix.length; y++){ -// for(int x = 0; x < matrix[0].length; x++){ -// -// // Create a new TextField in each Iteration -// Label tf = new Label(); -// tf.setStyle(matrix[y][x] == 0 ? colors[0] : colors[matrix[y][x]%4+1]); -// tf.setMinWidth(getCellWidth()); -// tf.setMinHeight(getCellHeight()); -// tf.setAlignment(Pos.CENTER); -// tf.setText(matrix[y][x] + ""); -// // Iterate the Index using the loops -// root.setRowIndex(tf,y); -// root.setColumnIndex(tf,x); -// root.setMargin(tf, new Insets(getVSpacing(),getHSpacing(),getVSpacing(),getHSpacing())); -// root.getChildren().add(tf); -// } -// } - valid = true; - } - - // FOR DEGBBUGING -// if(!getChildren().contains(root)) getChildren().add(root); -// root.resizeRelocate(0, 0, this.getWidth(), this.getHeight()); - - performingLayout = false; - } - - /** - * {@inheritDoc} - */ - @Override - public void requestLayout() { - if (performingLayout) { return; } - super.requestLayout(); - } - - /*************************************************************************** - * * - * Properties * - * * - **************************************************************************/ - /** - * the layout mode of Masonry Pane - */ - private ObjectProperty layoutMode = new SimpleObjectProperty<>(LayoutMode.MASONRY); - public final ObjectProperty layoutModeProperty() { - return this.layoutMode; - } - /** - * @return the LayoutMode of masonry pane - */ - public final LayoutMode getLayoutMode() { - return this.layoutModeProperty().get(); - } - /** - * sets the layout mode - * @param layoutMode to be used, either MASONRY or BIN_PACKING - */ - public final void setLayoutMode(final LayoutMode layoutMode) { - this.layoutModeProperty().set(layoutMode); - } - - - - /** - * the cell width of masonry grid - */ - private DoubleProperty cellWidth = new SimpleDoubleProperty(70); - public final DoubleProperty cellWidthProperty() { - return this.cellWidth; - } - /** - * @return the cell width of the masonry pane grid - */ - public final double getCellWidth() { - return this.cellWidthProperty().get(); - } - /** - * sets the cell width of the masonry pane grid - * @param cellWidth of the grid - */ - public final void setCellWidth(final double cellWidth) { - this.cellWidthProperty().set(cellWidth); - } - - - - /** - * the cell height of masonry grid - */ - private DoubleProperty cellHeight = new SimpleDoubleProperty(70); - public final DoubleProperty cellHeightProperty() { - return this.cellHeight; - } - /** - * @return the cell height of the masonry pane grid - */ - public final double getCellHeight() { - return this.cellHeightProperty().get(); - } - /** - * sets the cell height of the masonry pane grid - * @param cellHeight of the grid - */ - public final void setCellHeight(final double cellHeight) { - this.cellHeightProperty().set(cellHeight); - } - - - - /** - * horizontal spacing between nodes in grid - */ - private DoubleProperty hSpacing = new SimpleDoubleProperty(5); - public final DoubleProperty hSpacingProperty() { - return this.hSpacing; - } - /** - * @return the horizontal spacing between nodes in the grid - */ - public final double getHSpacing() { - return this.hSpacingProperty().get(); - } - /** - * sets the horizontal spacing in the grid - * @param spacing horizontal spacing - */ - public final void setHSpacing(final double spacing) { - this.hSpacingProperty().set(spacing); - } - - - - /** - * vertical spacing between nodes in the grid - */ - private DoubleProperty vSpacing = new SimpleDoubleProperty(5); - public final DoubleProperty vSpacingProperty() { - return this.vSpacing; - } - /** - * @return the vertical spacing between nodes in the grid - */ - public final double getVSpacing() { - return this.vSpacingProperty().get(); - } - /** - * sets the vertical spacing in the grid - * @param spacing vertical spacing - */ - public final void setVSpacing(final double spacing) { - this.vSpacingProperty().set(spacing); - } - - - - /** - * limit the grid columns to certain number - */ - private IntegerProperty limitColumn = new SimpleIntegerProperty(-1); - public final IntegerProperty limitColumnProperty() { - return this.limitColumn; - } - /** - * @return -1 if no limit on grid columns, else returns the maximum number of columns - * to be used in the grid - */ - public final int getLimitColumn() { - return this.limitColumnProperty().get(); - } - /** - * sets the column limit to be used in the grid - * @param limitColumn number of colummns to be used in the grid - */ - public final void setLimitColumn(final int limitColumn) { - this.limitColumnProperty().set(limitColumn); - } - - - - /** - * limit the grid rows to certain number - */ - private IntegerProperty limitRow = new SimpleIntegerProperty(-1); - public final IntegerProperty limitRowProperty() { - return this.limitRow; - } - /** - * @return -1 if no limit on grid rows, else returns the maximum number of rows - * to be used in the grid - */ - public final int getLimitRow() { - return this.limitRowProperty().get(); - } - /** - * sets the rows limit to be used in the grid - * @param limitRow number of rows to be used in the grid - */ - public final void setLimitRow(final int limitRow) { - this.limitRowProperty().set(limitRow); - } - - - /*************************************************************************** - * * - * Layout Modes * - * * - **************************************************************************/ - - public static abstract class LayoutMode { - public static final MasonryLayout MASONRY = new MasonryLayout(); - public static final BinPackingLayout BIN_PACKING = new BinPackingLayout(); - - protected abstract List fillGrid(int[][] matrix, List children, double cellWidth, double cellHeight, int limitRow, int limitCol, double gutterX, double gutterY); - - /** - * returns the available box at the cell (x,y) of the grid that fits the block if existed - * @param x - * @param y - * @param block - * @return - */ - protected BoundingBox getFreeArea(int[][] matrix, int x, int y, Region block, double cellWidth, double cellHeight, int limitRow, int limitCol, double gutterX, double gutterY){ - double blockHeight = getBLockHeight(block); - double blockWidth = getBLockWidth(block); - - int rowsNeeded = (int)Math.ceil(blockHeight/(cellHeight + gutterY)); - if(cellHeight*rowsNeeded + (rowsNeeded - 1)*2*gutterY < blockHeight) rowsNeeded++; - int maxRow = Math.min(x + rowsNeeded, limitRow); - - int colsNeeded = (int)Math.ceil(blockWidth/(cellWidth + gutterX)); - if(cellWidth*colsNeeded + (colsNeeded - 1)*2*gutterX < blockWidth) colsNeeded++; - int maxCol = Math.min(y + colsNeeded, limitCol); - - int minRow = maxRow; - int minCol = maxCol; - for(int i = x; i < minRow; i++){ - for(int j = y; j< maxCol; j++){ - if(matrix[i][j] !=0){ - if(y < j && j < minCol) minCol = j; - } - } - } - for(int i = x; i < maxRow; i++){ - for(int j = y; j< minCol; j++){ - if(matrix[i][j] !=0){ - if(x < i && i < minRow) minRow = i; - } - } - } - return new BoundingBox(x, y, minCol - y, minRow - x ); - } - - protected double getBLockWidth(Region region){ - if(region.getMinWidth()!= -1) return region.getMinWidth(); - if(region.getPrefWidth() != USE_COMPUTED_SIZE) return region.getPrefWidth(); - else return region.prefWidth(-1); - } - - protected double getBLockHeight(Region region){ - if(region.getMinHeight()!= -1) return region.getMinHeight(); - if(region.getPrefHeight() != USE_COMPUTED_SIZE) return region.getPrefHeight(); - else return region.prefHeight(getBLockWidth(region)); - } - - protected boolean validWidth(BoundingBox box , Region region, double cellW, double gutterX, double gutterY){ - boolean valid = false; - if(region.getMinWidth() != -1 && box.getWidth()*cellW + (box.getWidth()-1)*2*gutterX < region.getMinWidth()) return false; - - if(region.getPrefWidth() == USE_COMPUTED_SIZE && box.getWidth()*cellW + (box.getWidth()-1)*2*gutterX >= region.prefWidth(-1)) - valid = true; - if(region.getPrefWidth() != USE_COMPUTED_SIZE && box.getWidth()*cellW + (box.getWidth()-1)*2*gutterX >= region.getPrefWidth()) - valid = true; - return valid; - } - - protected boolean validHeight(BoundingBox box , Region region, double cellH, double gutterX, double gutterY){ - boolean valid = false; - if(region.getMinHeight() != -1 && box.getHeight()*cellH + (box.getHeight()-1)*2*gutterY < region.getMinHeight()) return false; - - if(region.getPrefHeight() == USE_COMPUTED_SIZE && box.getHeight()*cellH + (box.getHeight()-1)*2*gutterY >= region.prefHeight(region.prefWidth(-1))) - valid = true; - if(region.getPrefHeight() != USE_COMPUTED_SIZE && box.getHeight()*cellH + (box.getHeight()-1)*2*gutterY >= region.getPrefHeight()) - valid = true; - return valid; - } - - protected int[][] fillMatrix(int[][] matrix, int id, double row, double col, double width, double height){ - // int maxCol = (int) col; - // int maxRow = (int) row; - for(int x = (int)row; x < row + height;x++){ - for(int y = (int)col; y < col + width;y++){ - matrix[x][y] = id; - // if(++y > maxCol) maxCol = y; - } - // if(++x > maxRow) maxRow = x; - } - return matrix; - } - - } - - /*************************************************************************** - * * - * Masonry Layout * - * * - **************************************************************************/ - - private static class MasonryLayout extends LayoutMode { - @Override - public List fillGrid(int[][] matrix, List children, double cellWidth, double cellHeight, int limitRow, int limitCol, double gutterX, double gutterY) { - int row = matrix.length; - if(row <= 0) return null; - int col = matrix[0].length; - List boxes = new ArrayList<>(); - - for (int b = 0; b < children.size(); b++) { - Region block = (Region) children.get(b); - // for debugging purpose -// if(!(block instanceof GridPane)){ - for (int i = 0; i < row;i++) { - int old = boxes.size(); - for(int j = 0 ; j < col; j++){ - if(matrix[i][j] != 0) continue; - - // masonry condition - boolean isValidCell = true; - for(int k = i+1 ; k < row ; k++){ - if(matrix[k][j] !=0) { - isValidCell = false; - break; - } - } - if(!isValidCell) continue; - - BoundingBox box = getFreeArea(matrix, i, j, block, cellWidth, cellHeight, limitRow, limitCol, gutterX, gutterY); - if(!validWidth(box, block, cellWidth, gutterX, gutterY) || !validHeight(box,block, cellHeight, gutterX, gutterY)) - continue; - matrix = fillMatrix(matrix, b+1, box.getMinX(), box.getMinY(), box.getWidth(), box.getHeight()); - boxes.add(box); - break; - } - if(boxes.size()!=old) break; - if(i == row - 1){ - boxes.add(null); - } - } -// } - } - return boxes; - } - } - - /*************************************************************************** - * * - * Bin Packing Layout * - * * - **************************************************************************/ - private static class BinPackingLayout extends LayoutMode { - @Override - public List fillGrid(int[][] matrix, List children, double cellWidth, double cellHeight, int limitRow, int limitCol, double gutterX, double gutterY) { - int row = matrix.length; - if(row <= 0) return null; - int col = matrix[0].length; - List boxes = new ArrayList<>(); - - for (int b = 0; b < children.size(); b++) { - Region block = children.get(b); -// if(!(block instanceof GridPane)){ - for (int i = 0; i < row;i++) { - int old = boxes.size(); - for(int j = 0 ; j < col; j++){ - if(matrix[i][j] != 0) continue; - BoundingBox box = getFreeArea(matrix, i, j, block, cellWidth, cellHeight, limitRow, limitCol, gutterX, gutterY); - if(!validWidth(box, block, cellWidth, gutterX, gutterY) || !validHeight(box,block, cellHeight, gutterX, gutterY)) - continue; - matrix = fillMatrix(matrix, b+1, box.getMinX(), box.getMinY(), box.getWidth(), box.getHeight()); - boxes.add(box); - break; - } - if(boxes.size()!=old) break; - if(i == row - 1){ - boxes.add(null); - } - } -// } - } - return boxes; - } - } - -} +/* + * 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 com.jfoenix.transitions.CachedTransition; +import javafx.animation.*; +import javafx.beans.property.*; +import javafx.beans.value.ChangeListener; +import javafx.collections.ListChangeListener.Change; +import javafx.geometry.BoundingBox; +import javafx.scene.Node; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Pane; +import javafx.scene.layout.Region; +import javafx.util.Duration; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * A JFXMasonryPane implements asymmetrical grid layoutMode, it places the child nodes according to + * one of the modes: + * + *

Masonry Layout

Nodes will be added one after another, first in the horizontal direction + * , then vertically. sort of like a mason fitting stones in a wall. + * + *

Bin Packing Layout(First Fit)

it works similar to masonry layoutMode, however it tries to + * fill the empty gaps caused in masonry layoutMode. + * + * Note: childs that doesn't fit in the grid will be hidden. + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-05-24 + * + */ +public class JFXMasonryPane extends Pane { + + /*************************************************************************** + * * + * Private Fields * + * * + **************************************************************************/ + private boolean performingLayout = false; + // these variables are computed when layoutChildren is called + private int[][] matrix; + private HashMap animationMap = null; + private ParallelTransition trans = new ParallelTransition(); + private boolean valid = false; +// private GridPane root = new GridPane(); +// String[] colors = {"-fx-border-color:#EEEEEE", "-fx-border-color:RED;","-fx-border-color:BLUE; ","-fx-border-color:GREEN;", "-fx-border-color:PURPLE;"}; + private List oldBoxes; + + /*************************************************************************** + * * + * Constructor * + * * + **************************************************************************/ + /** + * Constructs a new JFXMasonryPane + */ + public JFXMasonryPane() { + this.widthProperty().addListener((o,oldVal,newVal)->{valid = false;}); + this.heightProperty().addListener((o,oldVal,newVal)->{valid = false;}); + ChangeListener layoutListener = (o,oldVal,newVal)->{ + valid = false; + this.requestLayout(); + }; + this.cellWidthProperty().addListener(layoutListener); + this.cellHeightProperty().addListener(layoutListener); + this.hSpacingProperty().addListener(layoutListener); + this.vSpacingProperty().addListener(layoutListener); + this.limitColumnProperty().addListener(layoutListener); + this.limitRowProperty().addListener(layoutListener); + this.getChildren().addListener((Change c)->{ + valid = false; + matrix = null; + this.requestLayout(); + }); + } + /*************************************************************************** + * * + * Override/Inherited methods * + * * + **************************************************************************/ + /** + * {@inheritDoc} + */ + @Override + protected void layoutChildren() { + performingLayout = true; + if(!valid){ + + int col, row; + col = (int) Math.floor(this.getWidth()/ (getCellWidth() + 2*getHSpacing())); + col = getLimitColumn() != -1 && col > getLimitColumn()? getLimitColumn() : col; + + if(matrix!=null && col == matrix[0].length) { + performingLayout = false; + return; + } + //(int) Math.floor(this.getHeight() / (cellH + 2*vSpacing)); + row = 100; + row = getLimitRow() != -1 && row > getLimitRow() ? getLimitRow() : row; + + matrix = new int[row][col]; + double minWidth = -1; + double minHeight = -1; + + List newBoxes; + List childs = new ArrayList<>(); + for(int i = 0 ; i < getChildren().size(); i++) + if(getChildren().get(i) instanceof Region) childs.add((Region) getChildren().get(i)); + newBoxes = layoutMode.get().fillGrid(matrix, childs, getCellWidth() , getCellHeight() , row, col, getHSpacing(), getVSpacing()); + + if(newBoxes == null){ + performingLayout = false; + return; + } + for (int i = 0; i < getChildren().size() && i < newBoxes.size(); i++) { + Region block = (Region) getChildren().get(i); + if(!(block instanceof GridPane)){ + double blockX, blockY, blockWidth, blockHeight; + if(newBoxes.get(i)!=null){ + blockX = newBoxes.get(i).getMinY()*getCellWidth() + ((newBoxes.get(i).getMinY()+1)*2-1)*getHSpacing(); + blockY = newBoxes.get(i).getMinX()* getCellHeight() + ((newBoxes.get(i).getMinX()+1)*2-1)*getVSpacing(); + blockWidth = newBoxes.get(i).getWidth()*getCellWidth() + (newBoxes.get(i).getWidth()-1)*2*getHSpacing(); + blockHeight = newBoxes.get(i).getHeight()* getCellHeight() + (newBoxes.get(i).getHeight()-1)*2*getVSpacing(); + }else{ + blockX = block.getLayoutX(); + blockY = block.getLayoutY(); + blockWidth = -1; + blockHeight = -1; + } + + if(animationMap == null){ + // init static children + block.setLayoutX(blockX); + block.setLayoutY(blockY); + block.setPrefSize(blockWidth, blockHeight); + block.resizeRelocate(blockX, blockY , blockWidth, blockHeight); + }else{ + if(oldBoxes == null || i >= oldBoxes.size()){ + // handle new children + block.setOpacity(0); + block.setLayoutX(blockX); + block.setLayoutY(blockY); + block.setPrefSize(blockWidth, blockHeight); + block.resizeRelocate(blockX, blockY , blockWidth, blockHeight); + } + + if(newBoxes.get(i)!=null){ + // handle children repositioning + animationMap.put(block, new CachedTransition(block, new Timeline(new KeyFrame(Duration.millis(2000), + new KeyValue(block.opacityProperty(), 1, Interpolator.LINEAR), + new KeyValue(block.layoutXProperty(), blockX, Interpolator.LINEAR), + new KeyValue(block.layoutYProperty(), blockY , Interpolator.LINEAR)))){{ + setCycleDuration(Duration.seconds(0.320)); + setDelay(Duration.seconds(0)); + setOnFinished((finish)->{ + block.setLayoutX(blockX); + block.setLayoutY(blockY); + block.setOpacity(1); + }); + }}); + + } else { + // handle children is being hidden ( cause it can't fit in the pane ) + animationMap.put(block, new CachedTransition(block, new Timeline(new KeyFrame(Duration.millis(2000), + new KeyValue(block.opacityProperty(), 0, Interpolator.LINEAR), + new KeyValue(block.layoutXProperty(), blockX, Interpolator.LINEAR), + new KeyValue(block.layoutYProperty(), blockY , Interpolator.LINEAR)))){{ + setCycleDuration(Duration.seconds(0.320)); + setDelay(Duration.seconds(0)); + setOnFinished((finish)->{ + block.setLayoutX(blockX); + block.setLayoutY(blockY); + block.setOpacity(0); + }); + }}); + } + + } + if(newBoxes.get(i)!=null){ + if(blockX + blockWidth> minWidth ) minWidth = blockX + blockWidth; + if(blockY + blockHeight > minHeight ) minHeight = blockY + blockHeight; + } + } + } + this.setMinSize(minWidth , minHeight); + if(animationMap == null) animationMap = new HashMap<>(); + + trans.stop(); + ParallelTransition newTransition = new ParallelTransition(); + newTransition.getChildren().addAll(animationMap.values()); + newTransition.play(); + trans = newTransition; + oldBoxes = newBoxes; + + // FOR DEGBBUGING + +// root.getChildren().clear(); +// for(int y = 0; y < matrix.length; y++){ +// for(int x = 0; x < matrix[0].length; x++){ +// +// // Create a new TextField in each Iteration +// Label tf = new Label(); +// tf.setStyle(matrix[y][x] == 0 ? colors[0] : colors[matrix[y][x]%4+1]); +// tf.setMinWidth(getCellWidth()); +// tf.setMinHeight(getCellHeight()); +// tf.setAlignment(Pos.CENTER); +// tf.setText(matrix[y][x] + ""); +// // Iterate the Index using the loops +// root.setRowIndex(tf,y); +// root.setColumnIndex(tf,x); +// root.setMargin(tf, new Insets(getVSpacing(),getHSpacing(),getVSpacing(),getHSpacing())); +// root.getChildren().add(tf); +// } +// } + valid = true; + } + + // FOR DEGBBUGING +// if(!getChildren().contains(root)) getChildren().add(root); +// root.resizeRelocate(0, 0, this.getWidth(), this.getHeight()); + + performingLayout = false; + } + + /** + * {@inheritDoc} + */ + @Override + public void requestLayout() { + if (performingLayout) { return; } + super.requestLayout(); + } + + /*************************************************************************** + * * + * Properties * + * * + **************************************************************************/ + /** + * the layout mode of Masonry Pane + */ + private ObjectProperty layoutMode = new SimpleObjectProperty<>(LayoutMode.MASONRY); + public final ObjectProperty layoutModeProperty() { + return this.layoutMode; + } + /** + * @return the LayoutMode of masonry pane + */ + public final LayoutMode getLayoutMode() { + return this.layoutModeProperty().get(); + } + /** + * sets the layout mode + * @param layoutMode to be used, either MASONRY or BIN_PACKING + */ + public final void setLayoutMode(final LayoutMode layoutMode) { + this.layoutModeProperty().set(layoutMode); + } + + + + /** + * the cell width of masonry grid + */ + private DoubleProperty cellWidth = new SimpleDoubleProperty(70); + public final DoubleProperty cellWidthProperty() { + return this.cellWidth; + } + /** + * @return the cell width of the masonry pane grid + */ + public final double getCellWidth() { + return this.cellWidthProperty().get(); + } + /** + * sets the cell width of the masonry pane grid + * @param cellWidth of the grid + */ + public final void setCellWidth(final double cellWidth) { + this.cellWidthProperty().set(cellWidth); + } + + + + /** + * the cell height of masonry grid + */ + private DoubleProperty cellHeight = new SimpleDoubleProperty(70); + public final DoubleProperty cellHeightProperty() { + return this.cellHeight; + } + /** + * @return the cell height of the masonry pane grid + */ + public final double getCellHeight() { + return this.cellHeightProperty().get(); + } + /** + * sets the cell height of the masonry pane grid + * @param cellHeight of the grid + */ + public final void setCellHeight(final double cellHeight) { + this.cellHeightProperty().set(cellHeight); + } + + + + /** + * horizontal spacing between nodes in grid + */ + private DoubleProperty hSpacing = new SimpleDoubleProperty(5); + public final DoubleProperty hSpacingProperty() { + return this.hSpacing; + } + /** + * @return the horizontal spacing between nodes in the grid + */ + public final double getHSpacing() { + return this.hSpacingProperty().get(); + } + /** + * sets the horizontal spacing in the grid + * @param spacing horizontal spacing + */ + public final void setHSpacing(final double spacing) { + this.hSpacingProperty().set(spacing); + } + + + + /** + * vertical spacing between nodes in the grid + */ + private DoubleProperty vSpacing = new SimpleDoubleProperty(5); + public final DoubleProperty vSpacingProperty() { + return this.vSpacing; + } + /** + * @return the vertical spacing between nodes in the grid + */ + public final double getVSpacing() { + return this.vSpacingProperty().get(); + } + /** + * sets the vertical spacing in the grid + * @param spacing vertical spacing + */ + public final void setVSpacing(final double spacing) { + this.vSpacingProperty().set(spacing); + } + + + + /** + * limit the grid columns to certain number + */ + private IntegerProperty limitColumn = new SimpleIntegerProperty(-1); + public final IntegerProperty limitColumnProperty() { + return this.limitColumn; + } + /** + * @return -1 if no limit on grid columns, else returns the maximum number of columns + * to be used in the grid + */ + public final int getLimitColumn() { + return this.limitColumnProperty().get(); + } + /** + * sets the column limit to be used in the grid + * @param limitColumn number of colummns to be used in the grid + */ + public final void setLimitColumn(final int limitColumn) { + this.limitColumnProperty().set(limitColumn); + } + + + + /** + * limit the grid rows to certain number + */ + private IntegerProperty limitRow = new SimpleIntegerProperty(-1); + public final IntegerProperty limitRowProperty() { + return this.limitRow; + } + /** + * @return -1 if no limit on grid rows, else returns the maximum number of rows + * to be used in the grid + */ + public final int getLimitRow() { + return this.limitRowProperty().get(); + } + /** + * sets the rows limit to be used in the grid + * @param limitRow number of rows to be used in the grid + */ + public final void setLimitRow(final int limitRow) { + this.limitRowProperty().set(limitRow); + } + + + /*************************************************************************** + * * + * Layout Modes * + * * + **************************************************************************/ + + public static abstract class LayoutMode { + public static final MasonryLayout MASONRY = new MasonryLayout(); + public static final BinPackingLayout BIN_PACKING = new BinPackingLayout(); + + protected abstract List fillGrid(int[][] matrix, List children, double cellWidth, double cellHeight, int limitRow, int limitCol, double gutterX, double gutterY); + + /** + * returns the available box at the cell (x,y) of the grid that fits the block if existed + * @param x + * @param y + * @param block + * @return + */ + protected BoundingBox getFreeArea(int[][] matrix, int x, int y, Region block, double cellWidth, double cellHeight, int limitRow, int limitCol, double gutterX, double gutterY){ + double blockHeight = getBLockHeight(block); + double blockWidth = getBLockWidth(block); + + int rowsNeeded = (int)Math.ceil(blockHeight/(cellHeight + gutterY)); + if(cellHeight*rowsNeeded + (rowsNeeded - 1)*2*gutterY < blockHeight) rowsNeeded++; + int maxRow = Math.min(x + rowsNeeded, limitRow); + + int colsNeeded = (int)Math.ceil(blockWidth/(cellWidth + gutterX)); + if(cellWidth*colsNeeded + (colsNeeded - 1)*2*gutterX < blockWidth) colsNeeded++; + int maxCol = Math.min(y + colsNeeded, limitCol); + + int minRow = maxRow; + int minCol = maxCol; + for(int i = x; i < minRow; i++){ + for(int j = y; j< maxCol; j++){ + if(matrix[i][j] !=0){ + if(y < j && j < minCol) minCol = j; + } + } + } + for(int i = x; i < maxRow; i++){ + for(int j = y; j< minCol; j++){ + if(matrix[i][j] !=0){ + if(x < i && i < minRow) minRow = i; + } + } + } + return new BoundingBox(x, y, minCol - y, minRow - x ); + } + + protected double getBLockWidth(Region region){ + if(region.getMinWidth()!= -1) return region.getMinWidth(); + if(region.getPrefWidth() != USE_COMPUTED_SIZE) return region.getPrefWidth(); + else return region.prefWidth(-1); + } + + protected double getBLockHeight(Region region){ + if(region.getMinHeight()!= -1) return region.getMinHeight(); + if(region.getPrefHeight() != USE_COMPUTED_SIZE) return region.getPrefHeight(); + else return region.prefHeight(getBLockWidth(region)); + } + + protected boolean validWidth(BoundingBox box , Region region, double cellW, double gutterX, double gutterY){ + boolean valid = false; + if(region.getMinWidth() != -1 && box.getWidth()*cellW + (box.getWidth()-1)*2*gutterX < region.getMinWidth()) return false; + + if(region.getPrefWidth() == USE_COMPUTED_SIZE && box.getWidth()*cellW + (box.getWidth()-1)*2*gutterX >= region.prefWidth(-1)) + valid = true; + if(region.getPrefWidth() != USE_COMPUTED_SIZE && box.getWidth()*cellW + (box.getWidth()-1)*2*gutterX >= region.getPrefWidth()) + valid = true; + return valid; + } + + protected boolean validHeight(BoundingBox box , Region region, double cellH, double gutterX, double gutterY){ + boolean valid = false; + if(region.getMinHeight() != -1 && box.getHeight()*cellH + (box.getHeight()-1)*2*gutterY < region.getMinHeight()) return false; + + if(region.getPrefHeight() == USE_COMPUTED_SIZE && box.getHeight()*cellH + (box.getHeight()-1)*2*gutterY >= region.prefHeight(region.prefWidth(-1))) + valid = true; + if(region.getPrefHeight() != USE_COMPUTED_SIZE && box.getHeight()*cellH + (box.getHeight()-1)*2*gutterY >= region.getPrefHeight()) + valid = true; + return valid; + } + + protected int[][] fillMatrix(int[][] matrix, int id, double row, double col, double width, double height){ + // int maxCol = (int) col; + // int maxRow = (int) row; + for(int x = (int)row; x < row + height;x++){ + for(int y = (int)col; y < col + width;y++){ + matrix[x][y] = id; + // if(++y > maxCol) maxCol = y; + } + // if(++x > maxRow) maxRow = x; + } + return matrix; + } + + } + + /*************************************************************************** + * * + * Masonry Layout * + * * + **************************************************************************/ + + private static class MasonryLayout extends LayoutMode { + @Override + public List fillGrid(int[][] matrix, List children, double cellWidth, double cellHeight, int limitRow, int limitCol, double gutterX, double gutterY) { + int row = matrix.length; + if(row <= 0) return null; + int col = matrix[0].length; + List boxes = new ArrayList<>(); + + for (int b = 0; b < children.size(); b++) { + Region block = (Region) children.get(b); + // for debugging purpose +// if(!(block instanceof GridPane)){ + for (int i = 0; i < row;i++) { + int old = boxes.size(); + for(int j = 0 ; j < col; j++){ + if(matrix[i][j] != 0) continue; + + // masonry condition + boolean isValidCell = true; + for(int k = i+1 ; k < row ; k++){ + if(matrix[k][j] !=0) { + isValidCell = false; + break; + } + } + if(!isValidCell) continue; + + BoundingBox box = getFreeArea(matrix, i, j, block, cellWidth, cellHeight, limitRow, limitCol, gutterX, gutterY); + if(!validWidth(box, block, cellWidth, gutterX, gutterY) || !validHeight(box,block, cellHeight, gutterX, gutterY)) + continue; + matrix = fillMatrix(matrix, b+1, box.getMinX(), box.getMinY(), box.getWidth(), box.getHeight()); + boxes.add(box); + break; + } + if(boxes.size()!=old) break; + if(i == row - 1){ + boxes.add(null); + } + } +// } + } + return boxes; + } + } + + /*************************************************************************** + * * + * Bin Packing Layout * + * * + **************************************************************************/ + private static class BinPackingLayout extends LayoutMode { + @Override + public List fillGrid(int[][] matrix, List children, double cellWidth, double cellHeight, int limitRow, int limitCol, double gutterX, double gutterY) { + int row = matrix.length; + if(row <= 0) return null; + int col = matrix[0].length; + List boxes = new ArrayList<>(); + + for (int b = 0; b < children.size(); b++) { + Region block = children.get(b); +// if(!(block instanceof GridPane)){ + for (int i = 0; i < row;i++) { + int old = boxes.size(); + for(int j = 0 ; j < col; j++){ + if(matrix[i][j] != 0) continue; + BoundingBox box = getFreeArea(matrix, i, j, block, cellWidth, cellHeight, limitRow, limitCol, gutterX, gutterY); + if(!validWidth(box, block, cellWidth, gutterX, gutterY) || !validHeight(box,block, cellHeight, gutterX, gutterY)) + continue; + matrix = fillMatrix(matrix, b+1, box.getMinX(), box.getMinY(), box.getWidth(), box.getHeight()); + boxes.add(box); + break; + } + if(boxes.size()!=old) break; + if(i == row - 1){ + boxes.add(null); + } + } +// } + } + return boxes; + } + } + +} diff --git a/src/com/jfoenix/controls/JFXNodesList.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXNodesList.java similarity index 97% rename from src/com/jfoenix/controls/JFXNodesList.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXNodesList.java index 73d6baf8..bda1d8af 100644 --- a/src/com/jfoenix/controls/JFXNodesList.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/JFXNodesList.java @@ -1,161 +1,161 @@ -/* - * 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.Animation.Status; -import javafx.animation.Interpolator; -import javafx.animation.KeyFrame; -import javafx.animation.KeyValue; -import javafx.animation.Timeline; -import javafx.scene.Node; -import javafx.scene.control.Button; -import javafx.scene.layout.Region; -import javafx.scene.layout.StackPane; -import javafx.scene.layout.VBox; -import javafx.util.Callback; -import javafx.util.Duration; - -import java.util.ArrayList; -import java.util.HashMap; - -/** - * list of nodes that are toggled On/Off by clicking on the 1st node - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXNodesList extends VBox { - - HashMap>> animationsMap = new HashMap<>(); - private boolean expanded = false; - private Timeline animateTimeline = new Timeline(); - - /** - * creates empty nodes list - */ - public JFXNodesList() { - this.setPickOnBounds(false); - this.getStyleClass().add("jfx-nodes-list"); - } - - /** - * add node to list. - * Note: this method must be called instead of getChildren().add(). - * - * @param node - */ - public void addAnimatedNode(Region node){ - addAnimatedNode(node, null); - } - - /** - * add node to list with a specified callback that is triggered after the node animation is finished. - * Note: this method must be called instead of getChildren().add(). - * - * @param node - */ - public void addAnimatedNode(Region node, Callback> animationCallBack ){ - // create container for the node if it's a sub nodes list - if(node instanceof JFXNodesList){ - StackPane container = new StackPane(node); - container.setPickOnBounds(false); - addAnimatedNode(container, animationCallBack); - return; - } - - // init node property - node.setVisible(false); - node.minWidthProperty().bind(node.prefWidthProperty()); - node.minHeightProperty().bind(node.prefHeightProperty()); - if(this.getChildren().size() > 0) initNode(node); - else { - if(node instanceof Button) ((Button)node).setOnAction((action)-> this.animateList()); - else node.setOnMouseClicked((click)-> this.animateList()); - node.getStyleClass().add("trigger-node"); - } - - // init the list height and width - if(this.getChildren().size() == 0 ){ - node.setVisible(true); - this.minHeightProperty().bind(node.prefHeightProperty()); - this.maxHeightProperty().bind(node.prefHeightProperty()); - this.minWidthProperty().bind(node.prefWidthProperty()); - this.maxWidthProperty().bind(node.prefWidthProperty()); - } - - // add the node and its listeners - this.getChildren().add(node); - this.rotateProperty().addListener((o,oldVal,newVal)-> node.setRotate(newVal.doubleValue() % 180 == 0 ? newVal.doubleValue() : -newVal.doubleValue())); - if(animationCallBack == null && this.getChildren().size() != 1) animationCallBack = (expanded)-> {return initDefaultAnimation(node, expanded);}; - else if (animationCallBack == null && this.getChildren().size() == 1 ) animationCallBack = (expanded)-> {return new ArrayList();}; - animationsMap.put(node, animationCallBack); - } - - /** - * animates the list to show/hide the nodes - */ - public void animateList(){ - expanded = !expanded; - - if(animateTimeline.getStatus().equals(Status.RUNNING)) animateTimeline.stop(); - - animateTimeline.getKeyFrames().clear(); - double duration = 120/(double)this.getChildren().size(); - - // show child nodes - if(expanded) this.getChildren().forEach(child->child.setVisible(true)); - - // add child nodes animation - for(int i = 1; i < this.getChildren().size();i++){ - Node child = this.getChildren().get(i); - ArrayList keyValues = animationsMap.get(child).call(expanded); - animateTimeline.getKeyFrames().add(new KeyFrame(Duration.millis(i*duration), keyValues.toArray(new KeyValue[keyValues.size()]))); - } - // add 1st element animation - ArrayList keyValues = animationsMap.get(this.getChildren().get(0)).call(expanded); - animateTimeline.getKeyFrames().add(new KeyFrame(Duration.millis(160), keyValues.toArray(new KeyValue[keyValues.size()]))); - - // hide child nodes to allow mouse events on the nodes behind them - if(!expanded) { - animateTimeline.setOnFinished((finish)->{ - for(int i = 1; i < this.getChildren().size();i++) - this.getChildren().get(i).setVisible(false); - }); - }else{ - animateTimeline.setOnFinished(null); - } - - animateTimeline.play(); - } - - protected void initNode(Node node){ - node.setScaleX(0); - node.setScaleY(0); - node.getStyleClass().add("sub-node"); - } - - // init default animation keyvalues - private ArrayList initDefaultAnimation(Region region, boolean expanded) { - ArrayList defaultAnimationValues = new ArrayList<>(); - defaultAnimationValues.add(new KeyValue(region.scaleXProperty(), expanded?1:0 , Interpolator.EASE_BOTH)); - defaultAnimationValues.add(new KeyValue(region.scaleYProperty(), expanded?1:0, Interpolator.EASE_BOTH)); - return defaultAnimationValues; - } -} +/* + * 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.Animation.Status; +import javafx.animation.Interpolator; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import javafx.util.Callback; +import javafx.util.Duration; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * list of nodes that are toggled On/Off by clicking on the 1st node + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXNodesList extends VBox { + + HashMap>> animationsMap = new HashMap<>(); + private boolean expanded = false; + private Timeline animateTimeline = new Timeline(); + + /** + * creates empty nodes list + */ + public JFXNodesList() { + this.setPickOnBounds(false); + this.getStyleClass().add("jfx-nodes-list"); + } + + /** + * add node to list. + * Note: this method must be called instead of getChildren().add(). + * + * @param node + */ + public void addAnimatedNode(Region node){ + addAnimatedNode(node, null); + } + + /** + * add node to list with a specified callback that is triggered after the node animation is finished. + * Note: this method must be called instead of getChildren().add(). + * + * @param node + */ + public void addAnimatedNode(Region node, Callback> animationCallBack ){ + // create container for the node if it's a sub nodes list + if(node instanceof JFXNodesList){ + StackPane container = new StackPane(node); + container.setPickOnBounds(false); + addAnimatedNode(container, animationCallBack); + return; + } + + // init node property + node.setVisible(false); + node.minWidthProperty().bind(node.prefWidthProperty()); + node.minHeightProperty().bind(node.prefHeightProperty()); + if(this.getChildren().size() > 0) initNode(node); + else { + if(node instanceof Button) ((Button)node).setOnAction((action)-> this.animateList()); + else node.setOnMouseClicked((click)-> this.animateList()); + node.getStyleClass().add("trigger-node"); + } + + // init the list height and width + if(this.getChildren().size() == 0 ){ + node.setVisible(true); + this.minHeightProperty().bind(node.prefHeightProperty()); + this.maxHeightProperty().bind(node.prefHeightProperty()); + this.minWidthProperty().bind(node.prefWidthProperty()); + this.maxWidthProperty().bind(node.prefWidthProperty()); + } + + // add the node and its listeners + this.getChildren().add(node); + this.rotateProperty().addListener((o,oldVal,newVal)-> node.setRotate(newVal.doubleValue() % 180 == 0 ? newVal.doubleValue() : -newVal.doubleValue())); + if(animationCallBack == null && this.getChildren().size() != 1) animationCallBack = (expanded)-> {return initDefaultAnimation(node, expanded);}; + else if (animationCallBack == null && this.getChildren().size() == 1 ) animationCallBack = (expanded)-> {return new ArrayList();}; + animationsMap.put(node, animationCallBack); + } + + /** + * animates the list to show/hide the nodes + */ + public void animateList(){ + expanded = !expanded; + + if(animateTimeline.getStatus().equals(Status.RUNNING)) animateTimeline.stop(); + + animateTimeline.getKeyFrames().clear(); + double duration = 120/(double)this.getChildren().size(); + + // show child nodes + if(expanded) this.getChildren().forEach(child->child.setVisible(true)); + + // add child nodes animation + for(int i = 1; i < this.getChildren().size();i++){ + Node child = this.getChildren().get(i); + ArrayList keyValues = animationsMap.get(child).call(expanded); + animateTimeline.getKeyFrames().add(new KeyFrame(Duration.millis(i*duration), keyValues.toArray(new KeyValue[keyValues.size()]))); + } + // add 1st element animation + ArrayList keyValues = animationsMap.get(this.getChildren().get(0)).call(expanded); + animateTimeline.getKeyFrames().add(new KeyFrame(Duration.millis(160), keyValues.toArray(new KeyValue[keyValues.size()]))); + + // hide child nodes to allow mouse events on the nodes behind them + if(!expanded) { + animateTimeline.setOnFinished((finish)->{ + for(int i = 1; i < this.getChildren().size();i++) + this.getChildren().get(i).setVisible(false); + }); + }else{ + animateTimeline.setOnFinished(null); + } + + animateTimeline.play(); + } + + protected void initNode(Node node){ + node.setScaleX(0); + node.setScaleY(0); + node.getStyleClass().add("sub-node"); + } + + // init default animation keyvalues + private ArrayList initDefaultAnimation(Region region, boolean expanded) { + ArrayList defaultAnimationValues = new ArrayList<>(); + defaultAnimationValues.add(new KeyValue(region.scaleXProperty(), expanded?1:0 , Interpolator.EASE_BOTH)); + defaultAnimationValues.add(new KeyValue(region.scaleYProperty(), expanded?1:0, Interpolator.EASE_BOTH)); + return defaultAnimationValues; + } +} diff --git a/src/com/jfoenix/controls/JFXPasswordField.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXPasswordField.java similarity index 97% rename from src/com/jfoenix/controls/JFXPasswordField.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXPasswordField.java index e65de578..2b43c497 100644 --- a/src/com/jfoenix/controls/JFXPasswordField.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/JFXPasswordField.java @@ -1,286 +1,286 @@ -/* - * 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 com.jfoenix.skins.JFXPasswordFieldSkin; -import com.jfoenix.validation.base.ValidatorBase; -import com.sun.javafx.css.converters.BooleanConverter; -import com.sun.javafx.css.converters.PaintConverter; -import javafx.beans.property.ReadOnlyObjectProperty; -import javafx.beans.property.ReadOnlyObjectWrapper; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.css.*; -import javafx.scene.control.Control; -import javafx.scene.control.PasswordField; -import javafx.scene.control.Skin; -import javafx.scene.paint.Color; -import javafx.scene.paint.Paint; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * JFXPasswordField is the material design implementation of a password Field. - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXPasswordField extends PasswordField { - - /** - * {@inheritDoc} - */ - public JFXPasswordField() { - super(); - initialize(); - } - - /** - * {@inheritDoc} - */ - @Override - protected Skin createDefaultSkin() { - return new JFXPasswordFieldSkin(this); - } - - private void initialize() { - this.getStyleClass().add(DEFAULT_STYLE_CLASS); - if(System.getProperty("java.vm.name").toLowerCase().equals("dalvik")){ - this.setStyle("-fx-skin: \"com.jfoenix.android.skins.JFXPasswordFieldSkinAndroid\";"); - } - } - - /** - * Initialize the style class to 'jfx-password-field'. - * - * This is the selector class from which CSS can be used to style - * this control. - */ - private static final String DEFAULT_STYLE_CLASS = "jfx-password-field"; - - /*************************************************************************** - * * - * Properties * - * * - **************************************************************************/ - - /** - * holds the current active validator on the password field in case of validation error - */ - private ReadOnlyObjectWrapper activeValidator = new ReadOnlyObjectWrapper(); - - public ValidatorBase getActiveValidator() { - return activeValidator == null ? null : activeValidator.get(); - } - - public ReadOnlyObjectProperty activeValidatorProperty() { - return this.activeValidator.getReadOnlyProperty(); - } - - /** - * list of validators that will validate the password value upon calling - * {{@link #validate()} - */ - private ObservableList validators = FXCollections.observableArrayList(); - - public ObservableList getValidators() { - return validators; - } - - public void setValidators(ValidatorBase... validators) { - this.validators.addAll(validators); - } - - /** - * validates the password value using the list of validators provided by the user - * {{@link #setValidators(ValidatorBase...)} - * @return true if the value is valid else false - */ - public boolean validate() { - for (ValidatorBase validator : validators) { - if (validator.getSrcControl() == null) - validator.setSrcControl(this); - validator.validate(); - if (validator.getHasErrors()) { - activeValidator.set(validator); - return false; - } - } - activeValidator.set(null); - return true; - } - - public void resetValidation() { - getStyleClass().remove(activeValidator.get() == null? "" : activeValidator.get().getErrorStyleClass()); - pseudoClassStateChanged(ValidatorBase.PSEUDO_CLASS_ERROR, false); - activeValidator.set(null); - } - - /*************************************************************************** - * * - * styleable Properties * - * * - **************************************************************************/ - - /** - * set true to show a float the prompt text when focusing the field - */ - private StyleableBooleanProperty labelFloat = new SimpleStyleableBooleanProperty(StyleableProperties.LABEL_FLOAT, JFXPasswordField.this, "lableFloat", false); - - public final StyleableBooleanProperty labelFloatProperty() { - return this.labelFloat; - } - - public final boolean isLabelFloat() { - return this.labelFloatProperty().get(); - } - - public final void setLabelFloat(final boolean labelFloat) { - this.labelFloatProperty().set(labelFloat); - } - - /** - * default color used when the field is unfocused - */ - private StyleableObjectProperty unFocusColor = new SimpleStyleableObjectProperty(StyleableProperties.UNFOCUS_COLOR, JFXPasswordField.this, "unFocusColor", Color.rgb(77, 77, 77)); - - public Paint getUnFocusColor() { - return unFocusColor == null ? Color.rgb(77, 77, 77) : unFocusColor.get(); - } - - public StyleableObjectProperty unFocusColorProperty() { - return this.unFocusColor; - } - - public void setUnFocusColor(Paint color) { - this.unFocusColor.set(color); - } - - /** - * default color used when the field is focused - */ - private StyleableObjectProperty focusColor = new SimpleStyleableObjectProperty(StyleableProperties.FOCUS_COLOR, JFXPasswordField.this, "focusColor", Color.valueOf("#4059A9")); - - public Paint getFocusColor() { - return focusColor == null ? Color.valueOf("#4059A9") : focusColor.get(); - } - - public StyleableObjectProperty focusColorProperty() { - return this.focusColor; - } - - public void setFocusColor(Paint color) { - this.focusColor.set(color); - } - - /** - * disable animation on validation - */ - private StyleableBooleanProperty disableAnimation = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_ANIMATION, JFXPasswordField.this, "disableAnimation", false); - public final StyleableBooleanProperty disableAnimationProperty() { - return this.disableAnimation; - } - public final Boolean isDisableAnimation() { - return disableAnimation == null ? false : this.disableAnimationProperty().get(); - } - public final void setDisableAnimation(final Boolean disabled) { - this.disableAnimationProperty().set(disabled); - } - - - private static class StyleableProperties { - private static final CssMetaData UNFOCUS_COLOR = new CssMetaData("-jfx-unfocus-color", PaintConverter.getInstance(), Color.valueOf("#A6A6A6")) { - @Override - public boolean isSettable(JFXPasswordField control) { - return control.unFocusColor == null || !control.unFocusColor.isBound(); - } - - @Override - public StyleableProperty getStyleableProperty(JFXPasswordField control) { - return control.unFocusColorProperty(); - } - }; - private static final CssMetaData FOCUS_COLOR = new CssMetaData("-jfx-focus-color", PaintConverter.getInstance(), Color.valueOf("#3f51b5")) { - @Override - public boolean isSettable(JFXPasswordField control) { - return control.focusColor == null || !control.focusColor.isBound(); - } - - @Override - public StyleableProperty getStyleableProperty(JFXPasswordField control) { - return control.focusColorProperty(); - } - }; - - private static final CssMetaData LABEL_FLOAT = new CssMetaData("-jfx-label-float", BooleanConverter.getInstance(), false) { - @Override - public boolean isSettable(JFXPasswordField control) { - return control.labelFloat == null || !control.labelFloat.isBound(); - } - - @Override - public StyleableBooleanProperty getStyleableProperty(JFXPasswordField control) { - return control.labelFloatProperty(); - } - }; - - private static final CssMetaData< JFXPasswordField, Boolean> DISABLE_ANIMATION = - new CssMetaData< JFXPasswordField, Boolean>("-fx-disable-animation", - BooleanConverter.getInstance(), false) { - @Override - public boolean isSettable(JFXPasswordField control) { - return control.disableAnimation == null || !control.disableAnimation.isBound(); - } - @Override - public StyleableBooleanProperty getStyleableProperty(JFXPasswordField control) { - return control.disableAnimationProperty(); - } - }; - - - private static final List> CHILD_STYLEABLES; - static { - final List> styleables = new ArrayList>(Control.getClassCssMetaData()); - Collections.addAll(styleables, UNFOCUS_COLOR, FOCUS_COLOR, LABEL_FLOAT, DISABLE_ANIMATION); - CHILD_STYLEABLES = Collections.unmodifiableList(styleables); - } - } - - // inherit the styleable properties from parent - private List> STYLEABLES; - - @Override - public List> getControlCssMetaData() { - if (STYLEABLES == null) { - final List> styleables = new ArrayList>(Control.getClassCssMetaData()); - styleables.addAll(getClassCssMetaData()); - styleables.addAll(super.getClassCssMetaData()); - STYLEABLES = Collections.unmodifiableList(styleables); - } - return STYLEABLES; - } - - public static List> getClassCssMetaData() { - return StyleableProperties.CHILD_STYLEABLES; - } - -} +/* + * 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 com.jfoenix.skins.JFXPasswordFieldSkin; +import com.jfoenix.validation.base.ValidatorBase; +import com.sun.javafx.css.converters.BooleanConverter; +import com.sun.javafx.css.converters.PaintConverter; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.css.*; +import javafx.scene.control.Control; +import javafx.scene.control.PasswordField; +import javafx.scene.control.Skin; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * JFXPasswordField is the material design implementation of a password Field. + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXPasswordField extends PasswordField { + + /** + * {@inheritDoc} + */ + public JFXPasswordField() { + super(); + initialize(); + } + + /** + * {@inheritDoc} + */ + @Override + protected Skin createDefaultSkin() { + return new JFXPasswordFieldSkin(this); + } + + private void initialize() { + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + if(System.getProperty("java.vm.name").toLowerCase().equals("dalvik")){ + this.setStyle("-fx-skin: \"com.jfoenix.android.skins.JFXPasswordFieldSkinAndroid\";"); + } + } + + /** + * Initialize the style class to 'jfx-password-field'. + * + * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-password-field"; + + /*************************************************************************** + * * + * Properties * + * * + **************************************************************************/ + + /** + * holds the current active validator on the password field in case of validation error + */ + private ReadOnlyObjectWrapper activeValidator = new ReadOnlyObjectWrapper(); + + public ValidatorBase getActiveValidator() { + return activeValidator == null ? null : activeValidator.get(); + } + + public ReadOnlyObjectProperty activeValidatorProperty() { + return this.activeValidator.getReadOnlyProperty(); + } + + /** + * list of validators that will validate the password value upon calling + * {{@link #validate()} + */ + private ObservableList validators = FXCollections.observableArrayList(); + + public ObservableList getValidators() { + return validators; + } + + public void setValidators(ValidatorBase... validators) { + this.validators.addAll(validators); + } + + /** + * validates the password value using the list of validators provided by the user + * {{@link #setValidators(ValidatorBase...)} + * @return true if the value is valid else false + */ + public boolean validate() { + for (ValidatorBase validator : validators) { + if (validator.getSrcControl() == null) + validator.setSrcControl(this); + validator.validate(); + if (validator.getHasErrors()) { + activeValidator.set(validator); + return false; + } + } + activeValidator.set(null); + return true; + } + + public void resetValidation() { + getStyleClass().remove(activeValidator.get() == null? "" : activeValidator.get().getErrorStyleClass()); + pseudoClassStateChanged(ValidatorBase.PSEUDO_CLASS_ERROR, false); + activeValidator.set(null); + } + + /*************************************************************************** + * * + * styleable Properties * + * * + **************************************************************************/ + + /** + * set true to show a float the prompt text when focusing the field + */ + private StyleableBooleanProperty labelFloat = new SimpleStyleableBooleanProperty(StyleableProperties.LABEL_FLOAT, JFXPasswordField.this, "lableFloat", false); + + public final StyleableBooleanProperty labelFloatProperty() { + return this.labelFloat; + } + + public final boolean isLabelFloat() { + return this.labelFloatProperty().get(); + } + + public final void setLabelFloat(final boolean labelFloat) { + this.labelFloatProperty().set(labelFloat); + } + + /** + * default color used when the field is unfocused + */ + private StyleableObjectProperty unFocusColor = new SimpleStyleableObjectProperty(StyleableProperties.UNFOCUS_COLOR, JFXPasswordField.this, "unFocusColor", Color.rgb(77, 77, 77)); + + public Paint getUnFocusColor() { + return unFocusColor == null ? Color.rgb(77, 77, 77) : unFocusColor.get(); + } + + public StyleableObjectProperty unFocusColorProperty() { + return this.unFocusColor; + } + + public void setUnFocusColor(Paint color) { + this.unFocusColor.set(color); + } + + /** + * default color used when the field is focused + */ + private StyleableObjectProperty focusColor = new SimpleStyleableObjectProperty(StyleableProperties.FOCUS_COLOR, JFXPasswordField.this, "focusColor", Color.valueOf("#4059A9")); + + public Paint getFocusColor() { + return focusColor == null ? Color.valueOf("#4059A9") : focusColor.get(); + } + + public StyleableObjectProperty focusColorProperty() { + return this.focusColor; + } + + public void setFocusColor(Paint color) { + this.focusColor.set(color); + } + + /** + * disable animation on validation + */ + private StyleableBooleanProperty disableAnimation = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_ANIMATION, JFXPasswordField.this, "disableAnimation", false); + public final StyleableBooleanProperty disableAnimationProperty() { + return this.disableAnimation; + } + public final Boolean isDisableAnimation() { + return disableAnimation == null ? false : this.disableAnimationProperty().get(); + } + public final void setDisableAnimation(final Boolean disabled) { + this.disableAnimationProperty().set(disabled); + } + + + private static class StyleableProperties { + private static final CssMetaData UNFOCUS_COLOR = new CssMetaData("-jfx-unfocus-color", PaintConverter.getInstance(), Color.valueOf("#A6A6A6")) { + @Override + public boolean isSettable(JFXPasswordField control) { + return control.unFocusColor == null || !control.unFocusColor.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXPasswordField control) { + return control.unFocusColorProperty(); + } + }; + private static final CssMetaData FOCUS_COLOR = new CssMetaData("-jfx-focus-color", PaintConverter.getInstance(), Color.valueOf("#3f51b5")) { + @Override + public boolean isSettable(JFXPasswordField control) { + return control.focusColor == null || !control.focusColor.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXPasswordField control) { + return control.focusColorProperty(); + } + }; + + private static final CssMetaData LABEL_FLOAT = new CssMetaData("-jfx-label-float", BooleanConverter.getInstance(), false) { + @Override + public boolean isSettable(JFXPasswordField control) { + return control.labelFloat == null || !control.labelFloat.isBound(); + } + + @Override + public StyleableBooleanProperty getStyleableProperty(JFXPasswordField control) { + return control.labelFloatProperty(); + } + }; + + private static final CssMetaData< JFXPasswordField, Boolean> DISABLE_ANIMATION = + new CssMetaData< JFXPasswordField, Boolean>("-fx-disable-animation", + BooleanConverter.getInstance(), false) { + @Override + public boolean isSettable(JFXPasswordField control) { + return control.disableAnimation == null || !control.disableAnimation.isBound(); + } + @Override + public StyleableBooleanProperty getStyleableProperty(JFXPasswordField control) { + return control.disableAnimationProperty(); + } + }; + + + private static final List> CHILD_STYLEABLES; + static { + final List> styleables = new ArrayList>(Control.getClassCssMetaData()); + Collections.addAll(styleables, UNFOCUS_COLOR, FOCUS_COLOR, LABEL_FLOAT, DISABLE_ANIMATION); + CHILD_STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + // inherit the styleable properties from parent + private List> STYLEABLES; + + @Override + public List> getControlCssMetaData() { + if (STYLEABLES == null) { + final List> styleables = new ArrayList>(Control.getClassCssMetaData()); + styleables.addAll(getClassCssMetaData()); + styleables.addAll(super.getClassCssMetaData()); + STYLEABLES = Collections.unmodifiableList(styleables); + } + return STYLEABLES; + } + + public static List> getClassCssMetaData() { + return StyleableProperties.CHILD_STYLEABLES; + } + +} diff --git a/src/com/jfoenix/controls/JFXPopup.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXPopup.java similarity index 100% rename from src/com/jfoenix/controls/JFXPopup.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXPopup.java diff --git a/src/com/jfoenix/controls/JFXProgressBar.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXProgressBar.java similarity index 96% rename from src/com/jfoenix/controls/JFXProgressBar.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXProgressBar.java index 2453aee8..fc658159 100644 --- a/src/com/jfoenix/controls/JFXProgressBar.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/JFXProgressBar.java @@ -1,71 +1,71 @@ -/* - * 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 com.jfoenix.skins.JFXProgressBarSkin; -import javafx.scene.control.ProgressBar; -import javafx.scene.control.Skin; - -/** - * JFXProgressBar is the material design implementation of a progress bar. - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXProgressBar extends ProgressBar { - - /** - * {@inheritDoc} - */ - public JFXProgressBar() { - super(); - initialize(); - } - - /** - * {@inheritDoc} - */ - public JFXProgressBar(double progress) { - super(progress); - initialize(); - } - - /** - * {@inheritDoc} - */ - @Override - protected Skin createDefaultSkin() { - return new JFXProgressBarSkin(this); - } - - private void initialize() { - setPrefWidth(200); - getStyleClass().add(DEFAULT_STYLE_CLASS); - } - - /** - * Initialize the style class to 'jfx-progress-bar'. - * - * This is the selector class from which CSS can be used to style - * this control. - */ - private static final String DEFAULT_STYLE_CLASS = "jfx-progress-bar"; - -} +/* + * 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 com.jfoenix.skins.JFXProgressBarSkin; +import javafx.scene.control.ProgressBar; +import javafx.scene.control.Skin; + +/** + * JFXProgressBar is the material design implementation of a progress bar. + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXProgressBar extends ProgressBar { + + /** + * {@inheritDoc} + */ + public JFXProgressBar() { + super(); + initialize(); + } + + /** + * {@inheritDoc} + */ + public JFXProgressBar(double progress) { + super(progress); + initialize(); + } + + /** + * {@inheritDoc} + */ + @Override + protected Skin createDefaultSkin() { + return new JFXProgressBarSkin(this); + } + + private void initialize() { + setPrefWidth(200); + getStyleClass().add(DEFAULT_STYLE_CLASS); + } + + /** + * Initialize the style class to 'jfx-progress-bar'. + * + * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-progress-bar"; + +} diff --git a/src/com/jfoenix/controls/JFXRadioButton.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXRadioButton.java similarity index 97% rename from src/com/jfoenix/controls/JFXRadioButton.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXRadioButton.java index cab6187f..bce4a4ed 100644 --- a/src/com/jfoenix/controls/JFXRadioButton.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/JFXRadioButton.java @@ -1,172 +1,172 @@ -/* - * 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 com.jfoenix.skins.JFXRadioButtonSkin; -import com.sun.javafx.css.converters.ColorConverter; -import javafx.css.*; -import javafx.scene.control.Control; -import javafx.scene.control.RadioButton; -import javafx.scene.control.Skin; -import javafx.scene.paint.Color; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * JFXRadioButton is the material design implementation of a radio button. - * - * @author Bashir Elias & Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXRadioButton extends RadioButton { - - /** - * {@inheritDoc} - */ - public JFXRadioButton(String text) { - super(text); - initialize(); - } - - /** - * {@inheritDoc} - */ - public JFXRadioButton() { - super(); - initialize(); - // init in scene builder workaround ( TODO : remove when JFoenix is well integrated in scenebuilder by gluon ) - StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); - for(int i = 0 ; i < stackTraceElements.length && i < 15; i++){ - if(stackTraceElements[i].getClassName().toLowerCase().contains(".scenebuilder.kit.fxom.")){ - this.setText("RadioButton"); - break; - } - } - } - - /** - * {@inheritDoc} - */ - @Override - protected Skin createDefaultSkin() { - return new JFXRadioButtonSkin(this); - } - - private void initialize() { - this.getStyleClass().add(DEFAULT_STYLE_CLASS); - } - - /** - * Initialize the style class to 'jfx-radio-button'. - * - * This is the selector class from which CSS can be used to style - * this control. - */ - private static final String DEFAULT_STYLE_CLASS = "jfx-radio-button"; - - /** - * default color used when the radio button is selected - */ - private StyleableObjectProperty selectedColor = new SimpleStyleableObjectProperty(StyleableProperties.SELECTED_COLOR, JFXRadioButton.this, "selectedColor", Color.valueOf("#0F9D58")); - - public final StyleableObjectProperty selectedColorProperty() { - return this.selectedColor; - } - public final Color getSelectedColor() { - return selectedColor == null ? Color.rgb(0, 0, 0, 0.2) : this.selectedColorProperty().get(); - } - public final void setSelectedColor(final Color selectedColor) { - this.selectedColorProperty().set(selectedColor); - } - - /** - * default color used when the radio button is not selected - */ - private StyleableObjectProperty unSelectedColor = new SimpleStyleableObjectProperty(StyleableProperties.UNSELECTED_COLOR, JFXRadioButton.this, "unSelectedColor", Color.valueOf("#5A5A5A")); - - public final StyleableObjectProperty unSelectedColorProperty() { - return this.unSelectedColor; - } - public final Color getUnSelectedColor() { - return unSelectedColor == null ? Color.TRANSPARENT : this.unSelectedColorProperty().get(); - } - public final void setUnSelectedColor(final Color unSelectedColor) { - this.unSelectedColorProperty().set(unSelectedColor); - } - - - private static class StyleableProperties { - private static final CssMetaData< JFXRadioButton, Color> SELECTED_COLOR = - new CssMetaData< JFXRadioButton, Color>("-jfx-selected-color", - ColorConverter.getInstance(), Color.valueOf("#0F9D58")) { - @Override - public boolean isSettable(JFXRadioButton control) { - return control.selectedColor == null || !control.selectedColor.isBound(); - } - @Override - public StyleableProperty getStyleableProperty(JFXRadioButton control) { - return control.selectedColorProperty(); - } - }; - private static final CssMetaData< JFXRadioButton, Color> UNSELECTED_COLOR = - new CssMetaData< JFXRadioButton, Color>("-jfx-unselected-color", - ColorConverter.getInstance(), Color.valueOf("#5A5A5A")) { - @Override - public boolean isSettable(JFXRadioButton control) { - return control.unSelectedColor == null || !control.unSelectedColor.isBound(); - } - @Override - public StyleableProperty getStyleableProperty(JFXRadioButton control) { - return control.unSelectedColorProperty(); - } - }; - - private static final List> CHILD_STYLEABLES; - static { - final List> styleables = - new ArrayList>(Control.getClassCssMetaData()); - Collections.addAll(styleables, - SELECTED_COLOR, - UNSELECTED_COLOR - ); - CHILD_STYLEABLES = Collections.unmodifiableList(styleables); - } - } - - // inherit the styleable properties from parent - private List> STYLEABLES; - - @Override - public List> getControlCssMetaData() { - if(STYLEABLES == null){ - final List> styleables = - new ArrayList>(Control.getClassCssMetaData()); - styleables.addAll(getClassCssMetaData()); - styleables.addAll(super.getClassCssMetaData()); - STYLEABLES = Collections.unmodifiableList(styleables); - } - return STYLEABLES; - } - public static List> getClassCssMetaData() { - return StyleableProperties.CHILD_STYLEABLES; - } -} +/* + * 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 com.jfoenix.skins.JFXRadioButtonSkin; +import com.sun.javafx.css.converters.ColorConverter; +import javafx.css.*; +import javafx.scene.control.Control; +import javafx.scene.control.RadioButton; +import javafx.scene.control.Skin; +import javafx.scene.paint.Color; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * JFXRadioButton is the material design implementation of a radio button. + * + * @author Bashir Elias & Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXRadioButton extends RadioButton { + + /** + * {@inheritDoc} + */ + public JFXRadioButton(String text) { + super(text); + initialize(); + } + + /** + * {@inheritDoc} + */ + public JFXRadioButton() { + super(); + initialize(); + // init in scene builder workaround ( TODO : remove when JFoenix is well integrated in scenebuilder by gluon ) + StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); + for(int i = 0 ; i < stackTraceElements.length && i < 15; i++){ + if(stackTraceElements[i].getClassName().toLowerCase().contains(".scenebuilder.kit.fxom.")){ + this.setText("RadioButton"); + break; + } + } + } + + /** + * {@inheritDoc} + */ + @Override + protected Skin createDefaultSkin() { + return new JFXRadioButtonSkin(this); + } + + private void initialize() { + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + } + + /** + * Initialize the style class to 'jfx-radio-button'. + * + * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-radio-button"; + + /** + * default color used when the radio button is selected + */ + private StyleableObjectProperty selectedColor = new SimpleStyleableObjectProperty(StyleableProperties.SELECTED_COLOR, JFXRadioButton.this, "selectedColor", Color.valueOf("#0F9D58")); + + public final StyleableObjectProperty selectedColorProperty() { + return this.selectedColor; + } + public final Color getSelectedColor() { + return selectedColor == null ? Color.rgb(0, 0, 0, 0.2) : this.selectedColorProperty().get(); + } + public final void setSelectedColor(final Color selectedColor) { + this.selectedColorProperty().set(selectedColor); + } + + /** + * default color used when the radio button is not selected + */ + private StyleableObjectProperty unSelectedColor = new SimpleStyleableObjectProperty(StyleableProperties.UNSELECTED_COLOR, JFXRadioButton.this, "unSelectedColor", Color.valueOf("#5A5A5A")); + + public final StyleableObjectProperty unSelectedColorProperty() { + return this.unSelectedColor; + } + public final Color getUnSelectedColor() { + return unSelectedColor == null ? Color.TRANSPARENT : this.unSelectedColorProperty().get(); + } + public final void setUnSelectedColor(final Color unSelectedColor) { + this.unSelectedColorProperty().set(unSelectedColor); + } + + + private static class StyleableProperties { + private static final CssMetaData< JFXRadioButton, Color> SELECTED_COLOR = + new CssMetaData< JFXRadioButton, Color>("-jfx-selected-color", + ColorConverter.getInstance(), Color.valueOf("#0F9D58")) { + @Override + public boolean isSettable(JFXRadioButton control) { + return control.selectedColor == null || !control.selectedColor.isBound(); + } + @Override + public StyleableProperty getStyleableProperty(JFXRadioButton control) { + return control.selectedColorProperty(); + } + }; + private static final CssMetaData< JFXRadioButton, Color> UNSELECTED_COLOR = + new CssMetaData< JFXRadioButton, Color>("-jfx-unselected-color", + ColorConverter.getInstance(), Color.valueOf("#5A5A5A")) { + @Override + public boolean isSettable(JFXRadioButton control) { + return control.unSelectedColor == null || !control.unSelectedColor.isBound(); + } + @Override + public StyleableProperty getStyleableProperty(JFXRadioButton control) { + return control.unSelectedColorProperty(); + } + }; + + private static final List> CHILD_STYLEABLES; + static { + final List> styleables = + new ArrayList>(Control.getClassCssMetaData()); + Collections.addAll(styleables, + SELECTED_COLOR, + UNSELECTED_COLOR + ); + CHILD_STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + // inherit the styleable properties from parent + private List> STYLEABLES; + + @Override + public List> getControlCssMetaData() { + if(STYLEABLES == null){ + final List> styleables = + new ArrayList>(Control.getClassCssMetaData()); + styleables.addAll(getClassCssMetaData()); + styleables.addAll(super.getClassCssMetaData()); + STYLEABLES = Collections.unmodifiableList(styleables); + } + return STYLEABLES; + } + public static List> getClassCssMetaData() { + return StyleableProperties.CHILD_STYLEABLES; + } +} diff --git a/src/com/jfoenix/controls/JFXRippler.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXRippler.java similarity index 97% rename from src/com/jfoenix/controls/JFXRippler.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXRippler.java index 6caeba26..cb18b251 100644 --- a/src/com/jfoenix/controls/JFXRippler.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/JFXRippler.java @@ -1,647 +1,647 @@ -/* - * 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 com.jfoenix.converters.RipplerMaskTypeConverter; -import com.jfoenix.transitions.CachedAnimation; -import com.jfoenix.transitions.CachedTransition; -import com.sun.javafx.css.converters.BooleanConverter; -import com.sun.javafx.css.converters.PaintConverter; -import com.sun.javafx.css.converters.SizeConverter; -import javafx.animation.Interpolator; -import javafx.animation.KeyFrame; -import javafx.animation.KeyValue; -import javafx.animation.Timeline; -import javafx.beans.DefaultProperty; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.css.*; -import javafx.event.Event; -import javafx.geometry.Bounds; -import javafx.geometry.Insets; -import javafx.scene.CacheHint; -import javafx.scene.Group; -import javafx.scene.Node; -import javafx.scene.Parent; -import javafx.scene.layout.*; -import javafx.scene.paint.Color; -import javafx.scene.paint.Paint; -import javafx.scene.shape.Circle; -import javafx.scene.shape.Rectangle; -import javafx.scene.shape.Shape; -import javafx.util.Duration; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * JFXRippler is the material design implementation of a ripple effect. - * the ripple effect can be applied to any node in the scene. JFXRippler is - * a {@link StackPane} container that holds a specified node (control node) and a ripple generator. - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -@DefaultProperty(value="control") -public class JFXRippler extends StackPane { - - public static enum RipplerPos{FRONT, BACK}; - public static enum RipplerMask{CIRCLE, RECT}; - - protected RippleGenerator rippler; - protected Pane ripplerPane; - protected Node control; - private boolean enabled = true; - private double RIPPLE_MAX_RADIUS = 300; - private Interpolator rippleInterpolator = Interpolator.SPLINE(0.0825, 0.3025, 0.0875, 0.9975); //0.1, 0.54, 0.28, 0.95); - - /** - * creates empty rippler node - */ - public JFXRippler(){ - this(null,RipplerMask.RECT,RipplerPos.FRONT); - } - - /** - * creates a rippler for the specified control - * - * @param control - */ - public JFXRippler(Node control){ - this(control, RipplerMask.RECT, RipplerPos.FRONT); - } - - /** - * creates a rippler for the specified control - * - * @param control - * @param pos can be either FRONT/BACK (position the ripple effect infront of or behind the control) - */ - public JFXRippler(Node control, RipplerPos pos){ - this(control, RipplerMask.RECT , pos); - } - - /** - * creates a rippler for the specified control and apply the specified mask to it - * - * @param control - * @param mask can be either rectangle/cricle - */ - public JFXRippler(Node control, RipplerMask mask){ - this(control, mask , RipplerPos.FRONT); - } - - /** - * creates a rippler for the specified control, mask and position. - * - * @param control - * @param mask can be either rectangle/cricle - * @param pos can be either FRONT/BACK (position the ripple effect infront of or behind the control) - */ - public JFXRippler(Node control, RipplerMask mask, RipplerPos pos){ - super(); - initialize(); - this.maskType.set(mask); - this.position.set(pos); - setControl(control); - setCache(true); - setCacheHint(CacheHint.SPEED); - setCacheShape(true); - setSnapToPixel(false); - } - - /*************************************************************************** - * * - * Setters / Getters * - * * - **************************************************************************/ - - public void setControl(Node control){ - if(control!=null){ - this.control = control; - - // create rippler panels - rippler = new RippleGenerator(); - ripplerPane = new StackPane(); - ripplerPane.getChildren().add(rippler); - - // set the control postion and listen if it's changed - if(this.position.get() == RipplerPos.BACK) ripplerPane.getChildren().add(this.control); - else this.getChildren().add(this.control); - - this.position.addListener((o,oldVal,newVal)->{ - if(this.position.get() == RipplerPos.BACK) ripplerPane.getChildren().add(this.control); - else this.getChildren().add(this.control); - }); - - this.getChildren().add(ripplerPane); - - // add listeners - initListeners(); - // if the control got resized the overlay rect must be rest - control.layoutBoundsProperty().addListener((o,oldVal,newVal)-> {resetOverLay(); resetClip();}); - control.boundsInParentProperty().addListener((o,oldVal,newVal)->{resetOverLay(); resetClip();}); - } - } - - public Node getControl(){ - return this.control; - } - - public void setEnabled(boolean enable){ - this.enabled = enable; - } - - // methods that can be changed by extending the rippler class - /** - * generate the clipping mask - * @return the mask node - */ - protected Node getMask(){ - double borderWidth = ripplerPane.getBorder() != null ? ripplerPane.getBorder().getInsets().getTop() : 0; - Bounds bounds = control.getBoundsInParent(); - double width = control.getLayoutBounds().getWidth(); - double height = control.getLayoutBounds().getHeight(); - double diffMinX = Math.abs(control.getBoundsInLocal().getMinX() - control.getLayoutBounds().getMinX()); - double diffMinY = Math.abs(control.getBoundsInLocal().getMinY() - control.getLayoutBounds().getMinY()); - double diffMaxX = Math.abs(control.getBoundsInLocal().getMaxX() - control.getLayoutBounds().getMaxX()); - double diffMaxY = Math.abs(control.getBoundsInLocal().getMaxY() - control.getLayoutBounds().getMaxY()); - Node mask; - switch(getMaskType()){ - case RECT: - mask = new Rectangle(bounds.getMinX()+diffMinX, bounds.getMinY() + diffMinY, width - 0.1 - 2 * borderWidth, height - 0.1 - 2 * borderWidth); // -0.1 to prevent resizing the anchor pane - break; - case CIRCLE: - double radius = Math.min((width / 2) - 0.1 - 2 * borderWidth, (height / 2) - 0.1 - 2 * borderWidth); - mask = new Circle((bounds.getMinX() + diffMinX + bounds.getMaxX() - diffMaxX) / 2 , (bounds.getMinY() + diffMinY + bounds.getMaxY() - diffMaxY) / 2, radius, Color.BLUE); - break; - default: - mask = new Rectangle(bounds.getMinX()+diffMinX, bounds.getMinY() + diffMinY, width - 0.1 - 2 * borderWidth, height - 0.1 - 2 * borderWidth); // -0.1 to prevent resizing the anchor pane - break; - } - if(control instanceof Shape || (control instanceof Region && ((Region)control).getShape()!=null)){ - mask = new StackPane(); - ((Region)mask).setShape((control instanceof Shape)? (Shape) control: ((Region)control).getShape()); - ((Region)mask).setBackground(new Background(new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY))); - mask.resize(width, height); - mask.relocate(bounds.getMinX()+diffMinX, bounds.getMinY() + diffMinY); - } - return mask; - } - - /** - * compute the ripple raddius - * @return the ripple radius size - */ - protected double computeRippleRadius() { - double width2 = control.getLayoutBounds().getWidth() * control.getLayoutBounds().getWidth(); - double height2 = control.getLayoutBounds().getHeight() * control.getLayoutBounds().getHeight(); - double radius = Math.min(Math.sqrt(width2 + height2), RIPPLE_MAX_RADIUS) * 1.1 + 5; - return radius; - } - - /** - * init mouse listeners on the rippler node - */ - protected void initListeners(){ - ripplerPane.setOnMousePressed((event) -> { - createRipple(event.getX(),event.getY()); - if(this.position.get() == RipplerPos.FRONT) - this.control.fireEvent(event); - }); - ripplerPane.setOnMouseReleased((event) -> { - if(this.position.get() == RipplerPos.FRONT) - this.control.fireEvent(event); - }); - ripplerPane.setOnMouseClicked((event) -> { - if(this.position.get() == RipplerPos.FRONT ) - this.control.fireEvent(event); - }); - } - - /** - * creates Ripple effect - */ - protected void createRipple(double x, double y){ - rippler.setGeneratorCenterX(x); - rippler.setGeneratorCenterY(y); - rippler.createMouseRipple(); - } - - /** - * fire event to the rippler pane manually - * @param event - */ - public void fireEventProgrammatically(Event event){ - if(!event.isConsumed()) - ripplerPane.fireEvent(event); - } - - public void showOverlay(){ - if(rippler.overlayRect!=null) rippler.overlayRect.outAnimation.stop(); - rippler.createOverlay(); - rippler.overlayRect.inAnimation.play(); - } - public void hideOverlay(){ - if(rippler.overlayRect!=null) rippler.overlayRect.inAnimation.stop(); - if(rippler.overlayRect!=null) rippler.overlayRect.outAnimation.play(); - } - - /** - * Generates ripples on the screen every 0.3 seconds or whenever - * the createRipple method is called. Ripples grow and fade out - * over 0.6 seconds - */ - class RippleGenerator extends Group { - - private double generatorCenterX = 0; - private double generatorCenterY = 0; - private OverLayRipple overlayRect; - private AtomicBoolean generating = new AtomicBoolean(false); - private boolean cacheRipplerClip = false; - private boolean resetClip = false; - - public RippleGenerator() { - /* - * improve in performance, by preventing - * redrawing the parent when the ripple effect is triggered - */ - this.setManaged(false); - } - public void createMouseRipple() { - if(enabled){ - if(!generating.getAndSet(true)){ - // create overlay once then change its color later - createOverlay(); - if(this.getClip() == null || (getChildren().size() == 1 && !cacheRipplerClip) || resetClip) this.setClip(getMask()); - this.resetClip = false; - - // create the ripple effect - final Ripple ripple = new Ripple(generatorCenterX, generatorCenterY); - getChildren().add(ripple); - - // animate the ripple - overlayRect.outAnimation.stop(); - overlayRect.inAnimation.play(); - ripple.inAnimation.getAnimation().play(); - - // create fade out transition for the ripple - ripplerPane.setOnMouseReleased((e)->{ - if(generating.getAndSet(false)){ - if(overlayRect!=null) overlayRect.inAnimation.stop(); - ripple.inAnimation.getAnimation().stop(); - ripple.outAnimation = new CachedAnimation(new Timeline(new KeyFrame(Duration.millis(Math.min(800, (0.9 * 500) / ripple.getScaleX())),ripple.outKeyValues)), this); - ripple.outAnimation.getAnimation().setOnFinished((event)-> getChildren().remove(ripple)); - ripple.outAnimation.getAnimation().play(); - if(overlayRect!=null) overlayRect.outAnimation.play(); - } - }); - } - } - } - - public Runnable createManualRipple(){ - if(enabled){ - if(!generating.getAndSet(true)){ - // create overlay once then change its color later - createOverlay(); - if(this.getClip() == null || (getChildren().size() == 1 && !cacheRipplerClip) || resetClip) this.setClip(getMask()); - this.resetClip = false; - - // create the ripple effect - final Ripple ripple = new Ripple(generatorCenterX, generatorCenterY); - getChildren().add(ripple); - - // animate the ripple - overlayRect.outAnimation.stop(); - overlayRect.inAnimation.play(); - ripple.inAnimation.getAnimation().play(); - - return ()->{ - // create fade out transition for the ripple - if(generating.getAndSet(false)){ - if(overlayRect!=null) overlayRect.inAnimation.stop(); - ripple.inAnimation.getAnimation().stop(); - ripple.outAnimation = new CachedAnimation(new Timeline(new KeyFrame(Duration.millis(Math.min(800, (0.9 * 500) / ripple.getScaleX())),ripple.outKeyValues)), this); - ripple.outAnimation.getAnimation().setOnFinished((event)-> getChildren().remove(ripple)); - ripple.outAnimation.getAnimation().play(); - if(overlayRect!=null) overlayRect.outAnimation.play(); - } - }; - } - } - return ()->{}; - } - - void cacheRippleClip(boolean cached){ - cacheRipplerClip = cached; - } - - public void createOverlay(){ - if(overlayRect == null){ - overlayRect = new OverLayRipple(); - overlayRect.setClip(getMask()); - getChildren().add(0, overlayRect); - overlayRect.setFill(new Color(((Color)ripplerFill.get()).getRed(), ((Color)ripplerFill.get()).getGreen(), ((Color)ripplerFill.get()).getBlue(),0.2)); - } - } - - public void setGeneratorCenterX(double generatorCenterX) { - this.generatorCenterX = generatorCenterX; - } - - public void setGeneratorCenterY(double generatorCenterY) { - this.generatorCenterY = generatorCenterY; - } - - private class OverLayRipple extends Rectangle{ - // Overlay ripple animations - CachedTransition inAnimation = new CachedTransition(this, new Timeline(new KeyFrame(Duration.millis(1300),new KeyValue(opacityProperty(), 1, Interpolator.EASE_IN)))){{setDelay(Duration.millis(0));setCycleDuration(Duration.millis(300));}}; - CachedTransition outAnimation = new CachedTransition(this, new Timeline(new KeyFrame(Duration.millis(1300),new KeyValue(opacityProperty(), 0, Interpolator.EASE_OUT)))){{setDelay(Duration.millis(0));setCycleDuration(Duration.millis(300));}}; - - public OverLayRipple() { - super(control.getLayoutBounds().getWidth() - 0.1,control.getLayoutBounds().getHeight() - 0.1); - this.getStyleClass().add("jfx-rippler-overlay"); - // this.widthProperty().bind(Bindings.createDoubleBinding(()-> control.getLayoutBounds().getWidth() - 0.1, control.boundsInParentProperty())); - // this.heightProperty().bind(Bindings.createDoubleBinding(()-> control.getLayoutBounds().getHeight() - 0.1, control.boundsInParentProperty())); - // update initial position - double diffMinX = Math.abs(control.getBoundsInLocal().getMinX() - control.getLayoutBounds().getMinX()); - double diffMinY = Math.abs(control.getBoundsInLocal().getMinY() - control.getLayoutBounds().getMinY()); - Bounds bounds = control.getBoundsInParent(); - this.setX(bounds.getMinX() + diffMinX); - this.setY(bounds.getMinY() + diffMinY); - // set initial attributes - this.setOpacity(0); - setCache(true); - setCacheHint(CacheHint.SPEED); - setCacheShape(true); - setSnapToPixel(false); - outAnimation.setOnFinished((finish)->resetOverLay()); - } - } - - private class Ripple extends Circle { - - KeyValue[] outKeyValues; - CachedAnimation outAnimation = null; - CachedAnimation inAnimation = null; - - private Ripple(double centerX, double centerY) { - super(centerX, centerY, ripplerRadius.get().doubleValue() == Region.USE_COMPUTED_SIZE ? computeRippleRadius() : ripplerRadius.get().doubleValue() , null); - - KeyValue[] inKeyValues = new KeyValue[isRipplerRecenter()? 4 : 2]; - outKeyValues = new KeyValue[isRipplerRecenter()? 5 : 3]; - - inKeyValues[0] = new KeyValue(scaleXProperty(), 0.9,rippleInterpolator); - inKeyValues[1] = new KeyValue(scaleYProperty(), 0.9,rippleInterpolator); - - outKeyValues[0] = new KeyValue(this.scaleXProperty(), 1 ,rippleInterpolator); - outKeyValues[1] = new KeyValue(this.scaleYProperty(), 1 ,rippleInterpolator); - outKeyValues[2] = new KeyValue(this.opacityProperty(), 0, rippleInterpolator); - - if(isRipplerRecenter()){ - double dx = (control.getLayoutBounds().getWidth()/2 - centerX) / 1.55; - double dy = (control.getLayoutBounds().getHeight()/2 - centerY) / 1.55; - inKeyValues[2] = outKeyValues[3] = new KeyValue(translateXProperty(), Math.signum(dx) * Math.min(Math.abs(dx), this.getRadius()/2), rippleInterpolator); - inKeyValues[3] = outKeyValues[4] = new KeyValue(translateYProperty(), Math.signum(dy) * Math.min(Math.abs(dy), this.getRadius()/2), rippleInterpolator); - } - inAnimation = new CachedAnimation(new Timeline(new KeyFrame(Duration.ZERO, - new KeyValue(scaleXProperty(), 0, rippleInterpolator), - new KeyValue(scaleYProperty(), 0, rippleInterpolator), - new KeyValue(translateXProperty(), 0, rippleInterpolator), - new KeyValue(translateYProperty(), 0, rippleInterpolator), - new KeyValue(opacityProperty(), 1, rippleInterpolator) - ),new KeyFrame(Duration.millis(900), inKeyValues)), this); - - setCache(true); - setCacheHint(CacheHint.SPEED); - setCacheShape(true); - setSnapToPixel(false); - setScaleX(0); - setScaleY(0); - if(ripplerFill.get() instanceof Color){ - Color circleColor = new Color(((Color)ripplerFill.get()).getRed(), ((Color)ripplerFill.get()).getGreen(), ((Color)ripplerFill.get()).getBlue(),0.3); - setStroke(circleColor); - setFill(circleColor); - }else{ - setStroke(ripplerFill.get()); - setFill(ripplerFill.get()); - } - } - } - - public void clear() { - getChildren().clear(); - generating.set(false); - } - } - - private void resetOverLay(){ - if(rippler.overlayRect!=null){ - rippler.overlayRect.inAnimation.stop(); - final RippleGenerator.OverLayRipple oldOverlay = rippler.overlayRect; - rippler.overlayRect.outAnimation.setOnFinished((finish)-> rippler.getChildren().remove(oldOverlay)); - rippler.overlayRect.outAnimation.play(); - rippler.overlayRect = null; - } - } - - private void resetClip(){ - this.rippler.resetClip = true; - } - - /*************************************************************************** - * * - * Stylesheet Handling * - * * - **************************************************************************/ - - /** - * Initialize the style class to 'jfx-rippler'. - * - * This is the selector class from which CSS can be used to style - * this control. - */ - private static final String DEFAULT_STYLE_CLASS = "jfx-rippler"; - - private void initialize() { - this.getStyleClass().add(DEFAULT_STYLE_CLASS); - } - - /** - * the ripple recenter property, by default it's false. - * if true the ripple effect will show gravitational pull to the center of its control - */ - private StyleableObjectProperty ripplerRecenter = new SimpleStyleableObjectProperty(StyleableProperties.RIPPLER_RECENTER, JFXRippler.this, "ripplerRecenter", false); - - public Boolean isRipplerRecenter(){ - return ripplerRecenter == null ? false : ripplerRecenter.get(); - } - public StyleableObjectProperty ripplerRecenterProperty(){ - return this.ripplerRecenter; - } - public void setRipplerRecenter(Boolean radius){ - this.ripplerRecenter.set(radius); - } - - /** - * the ripple radius size, by default it will be automatically computed. - */ - private StyleableObjectProperty ripplerRadius = new SimpleStyleableObjectProperty(StyleableProperties.RIPPLER_RADIUS, JFXRippler.this, "ripplerRadius", Region.USE_COMPUTED_SIZE); - - public Number getRipplerRadius(){ - return ripplerRadius == null ? Region.USE_COMPUTED_SIZE : ripplerRadius.get(); - } - public StyleableObjectProperty ripplerRadiusProperty(){ - return this.ripplerRadius; - } - public void setRipplerRadius(Number radius){ - this.ripplerRadius.set(radius); - } - - /** - * the default color of the ripple effect - */ - private StyleableObjectProperty ripplerFill = new SimpleStyleableObjectProperty(StyleableProperties.RIPPLER_FILL, JFXRippler.this, "ripplerFill", Color.rgb(0, 200, 255)); - - public Paint getRipplerFill(){ - return ripplerFill == null ? Color.rgb(0, 200, 255) : ripplerFill.get(); - } - public StyleableObjectProperty ripplerFillProperty(){ - return this.ripplerFill; - } - public void setRipplerFill(Paint color){ - this.ripplerFill.set(color); - } - - /** - * mask property used for clipping the rippler. - * can be either CIRCLE/RECT - */ - private StyleableObjectProperty maskType = new SimpleStyleableObjectProperty(StyleableProperties.MASK_TYPE, JFXRippler.this, "maskType", RipplerMask.RECT ); - - public RipplerMask getMaskType(){ - return maskType == null ? RipplerMask.RECT : maskType.get(); - } - public StyleableObjectProperty maskTypeProperty(){ - return this.maskType; - } - public void setMaskType(RipplerMask type){ - this.maskType.set(type); - } - - /** - * indicates whether the ripple effect is infront of or behind the node - */ - protected ObjectProperty position = new SimpleObjectProperty(); - - public void setPosition(RipplerPos pos){ - this.position.set(pos); - } - public RipplerPos getPosition(){ - return position == null ? RipplerPos.FRONT : position.get(); - } - public ObjectProperty positionProperty(){ - return this.position; - } - - - private static class StyleableProperties { - private static final CssMetaData< JFXRippler, Boolean> RIPPLER_RECENTER = - new CssMetaData< JFXRippler, Boolean>("-jfx-rippler-recenter", - BooleanConverter.getInstance(), false) { - @Override - public boolean isSettable(JFXRippler control) { - return control.ripplerRecenter == null || !control.ripplerRecenter.isBound(); - } - @Override - public StyleableProperty getStyleableProperty(JFXRippler control) { - return control.ripplerRecenterProperty(); - } - }; - private static final CssMetaData< JFXRippler, Paint> RIPPLER_FILL = - new CssMetaData< JFXRippler, Paint>("-jfx-rippler-fill", - PaintConverter.getInstance(), Color.rgb(0, 200, 255)) { - @Override - public boolean isSettable(JFXRippler control) { - return control.ripplerFill == null || !control.ripplerFill.isBound(); - } - @Override - public StyleableProperty getStyleableProperty(JFXRippler control) { - return control.ripplerFillProperty(); - } - }; - private static final CssMetaData< JFXRippler, Number> RIPPLER_RADIUS = - new CssMetaData< JFXRippler, Number>("-jfx-rippler-radius", - SizeConverter.getInstance(), Region.USE_COMPUTED_SIZE) { - @Override - public boolean isSettable(JFXRippler control) { - return control.ripplerRadius == null || !control.ripplerRadius.isBound(); - } - @Override - public StyleableProperty getStyleableProperty(JFXRippler control) { - return control.ripplerRadiusProperty(); - } - }; - private static final CssMetaData< JFXRippler, RipplerMask> MASK_TYPE = - new CssMetaData< JFXRippler, RipplerMask>("-jfx-mask-type", - RipplerMaskTypeConverter.getInstance(), RipplerMask.RECT) { - @Override - public boolean isSettable(JFXRippler control) { - return control.maskType == null || !control.maskType.isBound(); - } - @Override - public StyleableProperty getStyleableProperty(JFXRippler control) { - return control.maskTypeProperty(); - } - }; - - private static final List> STYLEABLES; - static { - final List> styleables = - new ArrayList>(Parent.getClassCssMetaData()); - Collections.addAll(styleables, - RIPPLER_RECENTER, - RIPPLER_RADIUS, - RIPPLER_FILL, - MASK_TYPE - ); - STYLEABLES = Collections.unmodifiableList(styleables); - } - } - - - @Override - public List> getCssMetaData() { - return getClassCssMetaData(); - } - public static List> getClassCssMetaData() { - return StyleableProperties.STYLEABLES; - } - - public Runnable createManualRipple() { - rippler.setGeneratorCenterX(control.getLayoutBounds().getWidth()/2); - rippler.setGeneratorCenterY(control.getLayoutBounds().getHeight()/2); - return rippler.createManualRipple(); - } - -} +/* + * 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 com.jfoenix.converters.RipplerMaskTypeConverter; +import com.jfoenix.transitions.CachedAnimation; +import com.jfoenix.transitions.CachedTransition; +import com.sun.javafx.css.converters.BooleanConverter; +import com.sun.javafx.css.converters.PaintConverter; +import com.sun.javafx.css.converters.SizeConverter; +import javafx.animation.Interpolator; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.beans.DefaultProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.css.*; +import javafx.event.Event; +import javafx.geometry.Bounds; +import javafx.geometry.Insets; +import javafx.scene.CacheHint; +import javafx.scene.Group; +import javafx.scene.Node; +import javafx.scene.Parent; +import javafx.scene.layout.*; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Rectangle; +import javafx.scene.shape.Shape; +import javafx.util.Duration; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * JFXRippler is the material design implementation of a ripple effect. + * the ripple effect can be applied to any node in the scene. JFXRippler is + * a {@link StackPane} container that holds a specified node (control node) and a ripple generator. + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +@DefaultProperty(value="control") +public class JFXRippler extends StackPane { + + public static enum RipplerPos{FRONT, BACK}; + public static enum RipplerMask{CIRCLE, RECT}; + + protected RippleGenerator rippler; + protected Pane ripplerPane; + protected Node control; + private boolean enabled = true; + private double RIPPLE_MAX_RADIUS = 300; + private Interpolator rippleInterpolator = Interpolator.SPLINE(0.0825, 0.3025, 0.0875, 0.9975); //0.1, 0.54, 0.28, 0.95); + + /** + * creates empty rippler node + */ + public JFXRippler(){ + this(null,RipplerMask.RECT,RipplerPos.FRONT); + } + + /** + * creates a rippler for the specified control + * + * @param control + */ + public JFXRippler(Node control){ + this(control, RipplerMask.RECT, RipplerPos.FRONT); + } + + /** + * creates a rippler for the specified control + * + * @param control + * @param pos can be either FRONT/BACK (position the ripple effect infront of or behind the control) + */ + public JFXRippler(Node control, RipplerPos pos){ + this(control, RipplerMask.RECT , pos); + } + + /** + * creates a rippler for the specified control and apply the specified mask to it + * + * @param control + * @param mask can be either rectangle/cricle + */ + public JFXRippler(Node control, RipplerMask mask){ + this(control, mask , RipplerPos.FRONT); + } + + /** + * creates a rippler for the specified control, mask and position. + * + * @param control + * @param mask can be either rectangle/cricle + * @param pos can be either FRONT/BACK (position the ripple effect infront of or behind the control) + */ + public JFXRippler(Node control, RipplerMask mask, RipplerPos pos){ + super(); + initialize(); + this.maskType.set(mask); + this.position.set(pos); + setControl(control); + setCache(true); + setCacheHint(CacheHint.SPEED); + setCacheShape(true); + setSnapToPixel(false); + } + + /*************************************************************************** + * * + * Setters / Getters * + * * + **************************************************************************/ + + public void setControl(Node control){ + if(control!=null){ + this.control = control; + + // create rippler panels + rippler = new RippleGenerator(); + ripplerPane = new StackPane(); + ripplerPane.getChildren().add(rippler); + + // set the control postion and listen if it's changed + if(this.position.get() == RipplerPos.BACK) ripplerPane.getChildren().add(this.control); + else this.getChildren().add(this.control); + + this.position.addListener((o,oldVal,newVal)->{ + if(this.position.get() == RipplerPos.BACK) ripplerPane.getChildren().add(this.control); + else this.getChildren().add(this.control); + }); + + this.getChildren().add(ripplerPane); + + // add listeners + initListeners(); + // if the control got resized the overlay rect must be rest + control.layoutBoundsProperty().addListener((o,oldVal,newVal)-> {resetOverLay(); resetClip();}); + control.boundsInParentProperty().addListener((o,oldVal,newVal)->{resetOverLay(); resetClip();}); + } + } + + public Node getControl(){ + return this.control; + } + + public void setEnabled(boolean enable){ + this.enabled = enable; + } + + // methods that can be changed by extending the rippler class + /** + * generate the clipping mask + * @return the mask node + */ + protected Node getMask(){ + double borderWidth = ripplerPane.getBorder() != null ? ripplerPane.getBorder().getInsets().getTop() : 0; + Bounds bounds = control.getBoundsInParent(); + double width = control.getLayoutBounds().getWidth(); + double height = control.getLayoutBounds().getHeight(); + double diffMinX = Math.abs(control.getBoundsInLocal().getMinX() - control.getLayoutBounds().getMinX()); + double diffMinY = Math.abs(control.getBoundsInLocal().getMinY() - control.getLayoutBounds().getMinY()); + double diffMaxX = Math.abs(control.getBoundsInLocal().getMaxX() - control.getLayoutBounds().getMaxX()); + double diffMaxY = Math.abs(control.getBoundsInLocal().getMaxY() - control.getLayoutBounds().getMaxY()); + Node mask; + switch(getMaskType()){ + case RECT: + mask = new Rectangle(bounds.getMinX()+diffMinX, bounds.getMinY() + diffMinY, width - 0.1 - 2 * borderWidth, height - 0.1 - 2 * borderWidth); // -0.1 to prevent resizing the anchor pane + break; + case CIRCLE: + double radius = Math.min((width / 2) - 0.1 - 2 * borderWidth, (height / 2) - 0.1 - 2 * borderWidth); + mask = new Circle((bounds.getMinX() + diffMinX + bounds.getMaxX() - diffMaxX) / 2 , (bounds.getMinY() + diffMinY + bounds.getMaxY() - diffMaxY) / 2, radius, Color.BLUE); + break; + default: + mask = new Rectangle(bounds.getMinX()+diffMinX, bounds.getMinY() + diffMinY, width - 0.1 - 2 * borderWidth, height - 0.1 - 2 * borderWidth); // -0.1 to prevent resizing the anchor pane + break; + } + if(control instanceof Shape || (control instanceof Region && ((Region)control).getShape()!=null)){ + mask = new StackPane(); + ((Region)mask).setShape((control instanceof Shape)? (Shape) control: ((Region)control).getShape()); + ((Region)mask).setBackground(new Background(new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY))); + mask.resize(width, height); + mask.relocate(bounds.getMinX()+diffMinX, bounds.getMinY() + diffMinY); + } + return mask; + } + + /** + * compute the ripple raddius + * @return the ripple radius size + */ + protected double computeRippleRadius() { + double width2 = control.getLayoutBounds().getWidth() * control.getLayoutBounds().getWidth(); + double height2 = control.getLayoutBounds().getHeight() * control.getLayoutBounds().getHeight(); + double radius = Math.min(Math.sqrt(width2 + height2), RIPPLE_MAX_RADIUS) * 1.1 + 5; + return radius; + } + + /** + * init mouse listeners on the rippler node + */ + protected void initListeners(){ + ripplerPane.setOnMousePressed((event) -> { + createRipple(event.getX(),event.getY()); + if(this.position.get() == RipplerPos.FRONT) + this.control.fireEvent(event); + }); + ripplerPane.setOnMouseReleased((event) -> { + if(this.position.get() == RipplerPos.FRONT) + this.control.fireEvent(event); + }); + ripplerPane.setOnMouseClicked((event) -> { + if(this.position.get() == RipplerPos.FRONT ) + this.control.fireEvent(event); + }); + } + + /** + * creates Ripple effect + */ + protected void createRipple(double x, double y){ + rippler.setGeneratorCenterX(x); + rippler.setGeneratorCenterY(y); + rippler.createMouseRipple(); + } + + /** + * fire event to the rippler pane manually + * @param event + */ + public void fireEventProgrammatically(Event event){ + if(!event.isConsumed()) + ripplerPane.fireEvent(event); + } + + public void showOverlay(){ + if(rippler.overlayRect!=null) rippler.overlayRect.outAnimation.stop(); + rippler.createOverlay(); + rippler.overlayRect.inAnimation.play(); + } + public void hideOverlay(){ + if(rippler.overlayRect!=null) rippler.overlayRect.inAnimation.stop(); + if(rippler.overlayRect!=null) rippler.overlayRect.outAnimation.play(); + } + + /** + * Generates ripples on the screen every 0.3 seconds or whenever + * the createRipple method is called. Ripples grow and fade out + * over 0.6 seconds + */ + class RippleGenerator extends Group { + + private double generatorCenterX = 0; + private double generatorCenterY = 0; + private OverLayRipple overlayRect; + private AtomicBoolean generating = new AtomicBoolean(false); + private boolean cacheRipplerClip = false; + private boolean resetClip = false; + + public RippleGenerator() { + /* + * improve in performance, by preventing + * redrawing the parent when the ripple effect is triggered + */ + this.setManaged(false); + } + public void createMouseRipple() { + if(enabled){ + if(!generating.getAndSet(true)){ + // create overlay once then change its color later + createOverlay(); + if(this.getClip() == null || (getChildren().size() == 1 && !cacheRipplerClip) || resetClip) this.setClip(getMask()); + this.resetClip = false; + + // create the ripple effect + final Ripple ripple = new Ripple(generatorCenterX, generatorCenterY); + getChildren().add(ripple); + + // animate the ripple + overlayRect.outAnimation.stop(); + overlayRect.inAnimation.play(); + ripple.inAnimation.getAnimation().play(); + + // create fade out transition for the ripple + ripplerPane.setOnMouseReleased((e)->{ + if(generating.getAndSet(false)){ + if(overlayRect!=null) overlayRect.inAnimation.stop(); + ripple.inAnimation.getAnimation().stop(); + ripple.outAnimation = new CachedAnimation(new Timeline(new KeyFrame(Duration.millis(Math.min(800, (0.9 * 500) / ripple.getScaleX())),ripple.outKeyValues)), this); + ripple.outAnimation.getAnimation().setOnFinished((event)-> getChildren().remove(ripple)); + ripple.outAnimation.getAnimation().play(); + if(overlayRect!=null) overlayRect.outAnimation.play(); + } + }); + } + } + } + + public Runnable createManualRipple(){ + if(enabled){ + if(!generating.getAndSet(true)){ + // create overlay once then change its color later + createOverlay(); + if(this.getClip() == null || (getChildren().size() == 1 && !cacheRipplerClip) || resetClip) this.setClip(getMask()); + this.resetClip = false; + + // create the ripple effect + final Ripple ripple = new Ripple(generatorCenterX, generatorCenterY); + getChildren().add(ripple); + + // animate the ripple + overlayRect.outAnimation.stop(); + overlayRect.inAnimation.play(); + ripple.inAnimation.getAnimation().play(); + + return ()->{ + // create fade out transition for the ripple + if(generating.getAndSet(false)){ + if(overlayRect!=null) overlayRect.inAnimation.stop(); + ripple.inAnimation.getAnimation().stop(); + ripple.outAnimation = new CachedAnimation(new Timeline(new KeyFrame(Duration.millis(Math.min(800, (0.9 * 500) / ripple.getScaleX())),ripple.outKeyValues)), this); + ripple.outAnimation.getAnimation().setOnFinished((event)-> getChildren().remove(ripple)); + ripple.outAnimation.getAnimation().play(); + if(overlayRect!=null) overlayRect.outAnimation.play(); + } + }; + } + } + return ()->{}; + } + + void cacheRippleClip(boolean cached){ + cacheRipplerClip = cached; + } + + public void createOverlay(){ + if(overlayRect == null){ + overlayRect = new OverLayRipple(); + overlayRect.setClip(getMask()); + getChildren().add(0, overlayRect); + overlayRect.setFill(new Color(((Color)ripplerFill.get()).getRed(), ((Color)ripplerFill.get()).getGreen(), ((Color)ripplerFill.get()).getBlue(),0.2)); + } + } + + public void setGeneratorCenterX(double generatorCenterX) { + this.generatorCenterX = generatorCenterX; + } + + public void setGeneratorCenterY(double generatorCenterY) { + this.generatorCenterY = generatorCenterY; + } + + private class OverLayRipple extends Rectangle{ + // Overlay ripple animations + CachedTransition inAnimation = new CachedTransition(this, new Timeline(new KeyFrame(Duration.millis(1300),new KeyValue(opacityProperty(), 1, Interpolator.EASE_IN)))){{setDelay(Duration.millis(0));setCycleDuration(Duration.millis(300));}}; + CachedTransition outAnimation = new CachedTransition(this, new Timeline(new KeyFrame(Duration.millis(1300),new KeyValue(opacityProperty(), 0, Interpolator.EASE_OUT)))){{setDelay(Duration.millis(0));setCycleDuration(Duration.millis(300));}}; + + public OverLayRipple() { + super(control.getLayoutBounds().getWidth() - 0.1,control.getLayoutBounds().getHeight() - 0.1); + this.getStyleClass().add("jfx-rippler-overlay"); + // this.widthProperty().bind(Bindings.createDoubleBinding(()-> control.getLayoutBounds().getWidth() - 0.1, control.boundsInParentProperty())); + // this.heightProperty().bind(Bindings.createDoubleBinding(()-> control.getLayoutBounds().getHeight() - 0.1, control.boundsInParentProperty())); + // update initial position + double diffMinX = Math.abs(control.getBoundsInLocal().getMinX() - control.getLayoutBounds().getMinX()); + double diffMinY = Math.abs(control.getBoundsInLocal().getMinY() - control.getLayoutBounds().getMinY()); + Bounds bounds = control.getBoundsInParent(); + this.setX(bounds.getMinX() + diffMinX); + this.setY(bounds.getMinY() + diffMinY); + // set initial attributes + this.setOpacity(0); + setCache(true); + setCacheHint(CacheHint.SPEED); + setCacheShape(true); + setSnapToPixel(false); + outAnimation.setOnFinished((finish)->resetOverLay()); + } + } + + private class Ripple extends Circle { + + KeyValue[] outKeyValues; + CachedAnimation outAnimation = null; + CachedAnimation inAnimation = null; + + private Ripple(double centerX, double centerY) { + super(centerX, centerY, ripplerRadius.get().doubleValue() == Region.USE_COMPUTED_SIZE ? computeRippleRadius() : ripplerRadius.get().doubleValue() , null); + + KeyValue[] inKeyValues = new KeyValue[isRipplerRecenter()? 4 : 2]; + outKeyValues = new KeyValue[isRipplerRecenter()? 5 : 3]; + + inKeyValues[0] = new KeyValue(scaleXProperty(), 0.9,rippleInterpolator); + inKeyValues[1] = new KeyValue(scaleYProperty(), 0.9,rippleInterpolator); + + outKeyValues[0] = new KeyValue(this.scaleXProperty(), 1 ,rippleInterpolator); + outKeyValues[1] = new KeyValue(this.scaleYProperty(), 1 ,rippleInterpolator); + outKeyValues[2] = new KeyValue(this.opacityProperty(), 0, rippleInterpolator); + + if(isRipplerRecenter()){ + double dx = (control.getLayoutBounds().getWidth()/2 - centerX) / 1.55; + double dy = (control.getLayoutBounds().getHeight()/2 - centerY) / 1.55; + inKeyValues[2] = outKeyValues[3] = new KeyValue(translateXProperty(), Math.signum(dx) * Math.min(Math.abs(dx), this.getRadius()/2), rippleInterpolator); + inKeyValues[3] = outKeyValues[4] = new KeyValue(translateYProperty(), Math.signum(dy) * Math.min(Math.abs(dy), this.getRadius()/2), rippleInterpolator); + } + inAnimation = new CachedAnimation(new Timeline(new KeyFrame(Duration.ZERO, + new KeyValue(scaleXProperty(), 0, rippleInterpolator), + new KeyValue(scaleYProperty(), 0, rippleInterpolator), + new KeyValue(translateXProperty(), 0, rippleInterpolator), + new KeyValue(translateYProperty(), 0, rippleInterpolator), + new KeyValue(opacityProperty(), 1, rippleInterpolator) + ),new KeyFrame(Duration.millis(900), inKeyValues)), this); + + setCache(true); + setCacheHint(CacheHint.SPEED); + setCacheShape(true); + setSnapToPixel(false); + setScaleX(0); + setScaleY(0); + if(ripplerFill.get() instanceof Color){ + Color circleColor = new Color(((Color)ripplerFill.get()).getRed(), ((Color)ripplerFill.get()).getGreen(), ((Color)ripplerFill.get()).getBlue(),0.3); + setStroke(circleColor); + setFill(circleColor); + }else{ + setStroke(ripplerFill.get()); + setFill(ripplerFill.get()); + } + } + } + + public void clear() { + getChildren().clear(); + generating.set(false); + } + } + + private void resetOverLay(){ + if(rippler.overlayRect!=null){ + rippler.overlayRect.inAnimation.stop(); + final RippleGenerator.OverLayRipple oldOverlay = rippler.overlayRect; + rippler.overlayRect.outAnimation.setOnFinished((finish)-> rippler.getChildren().remove(oldOverlay)); + rippler.overlayRect.outAnimation.play(); + rippler.overlayRect = null; + } + } + + private void resetClip(){ + this.rippler.resetClip = true; + } + + /*************************************************************************** + * * + * Stylesheet Handling * + * * + **************************************************************************/ + + /** + * Initialize the style class to 'jfx-rippler'. + * + * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-rippler"; + + private void initialize() { + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + } + + /** + * the ripple recenter property, by default it's false. + * if true the ripple effect will show gravitational pull to the center of its control + */ + private StyleableObjectProperty ripplerRecenter = new SimpleStyleableObjectProperty(StyleableProperties.RIPPLER_RECENTER, JFXRippler.this, "ripplerRecenter", false); + + public Boolean isRipplerRecenter(){ + return ripplerRecenter == null ? false : ripplerRecenter.get(); + } + public StyleableObjectProperty ripplerRecenterProperty(){ + return this.ripplerRecenter; + } + public void setRipplerRecenter(Boolean radius){ + this.ripplerRecenter.set(radius); + } + + /** + * the ripple radius size, by default it will be automatically computed. + */ + private StyleableObjectProperty ripplerRadius = new SimpleStyleableObjectProperty(StyleableProperties.RIPPLER_RADIUS, JFXRippler.this, "ripplerRadius", Region.USE_COMPUTED_SIZE); + + public Number getRipplerRadius(){ + return ripplerRadius == null ? Region.USE_COMPUTED_SIZE : ripplerRadius.get(); + } + public StyleableObjectProperty ripplerRadiusProperty(){ + return this.ripplerRadius; + } + public void setRipplerRadius(Number radius){ + this.ripplerRadius.set(radius); + } + + /** + * the default color of the ripple effect + */ + private StyleableObjectProperty ripplerFill = new SimpleStyleableObjectProperty(StyleableProperties.RIPPLER_FILL, JFXRippler.this, "ripplerFill", Color.rgb(0, 200, 255)); + + public Paint getRipplerFill(){ + return ripplerFill == null ? Color.rgb(0, 200, 255) : ripplerFill.get(); + } + public StyleableObjectProperty ripplerFillProperty(){ + return this.ripplerFill; + } + public void setRipplerFill(Paint color){ + this.ripplerFill.set(color); + } + + /** + * mask property used for clipping the rippler. + * can be either CIRCLE/RECT + */ + private StyleableObjectProperty maskType = new SimpleStyleableObjectProperty(StyleableProperties.MASK_TYPE, JFXRippler.this, "maskType", RipplerMask.RECT ); + + public RipplerMask getMaskType(){ + return maskType == null ? RipplerMask.RECT : maskType.get(); + } + public StyleableObjectProperty maskTypeProperty(){ + return this.maskType; + } + public void setMaskType(RipplerMask type){ + this.maskType.set(type); + } + + /** + * indicates whether the ripple effect is infront of or behind the node + */ + protected ObjectProperty position = new SimpleObjectProperty(); + + public void setPosition(RipplerPos pos){ + this.position.set(pos); + } + public RipplerPos getPosition(){ + return position == null ? RipplerPos.FRONT : position.get(); + } + public ObjectProperty positionProperty(){ + return this.position; + } + + + private static class StyleableProperties { + private static final CssMetaData< JFXRippler, Boolean> RIPPLER_RECENTER = + new CssMetaData< JFXRippler, Boolean>("-jfx-rippler-recenter", + BooleanConverter.getInstance(), false) { + @Override + public boolean isSettable(JFXRippler control) { + return control.ripplerRecenter == null || !control.ripplerRecenter.isBound(); + } + @Override + public StyleableProperty getStyleableProperty(JFXRippler control) { + return control.ripplerRecenterProperty(); + } + }; + private static final CssMetaData< JFXRippler, Paint> RIPPLER_FILL = + new CssMetaData< JFXRippler, Paint>("-jfx-rippler-fill", + PaintConverter.getInstance(), Color.rgb(0, 200, 255)) { + @Override + public boolean isSettable(JFXRippler control) { + return control.ripplerFill == null || !control.ripplerFill.isBound(); + } + @Override + public StyleableProperty getStyleableProperty(JFXRippler control) { + return control.ripplerFillProperty(); + } + }; + private static final CssMetaData< JFXRippler, Number> RIPPLER_RADIUS = + new CssMetaData< JFXRippler, Number>("-jfx-rippler-radius", + SizeConverter.getInstance(), Region.USE_COMPUTED_SIZE) { + @Override + public boolean isSettable(JFXRippler control) { + return control.ripplerRadius == null || !control.ripplerRadius.isBound(); + } + @Override + public StyleableProperty getStyleableProperty(JFXRippler control) { + return control.ripplerRadiusProperty(); + } + }; + private static final CssMetaData< JFXRippler, RipplerMask> MASK_TYPE = + new CssMetaData< JFXRippler, RipplerMask>("-jfx-mask-type", + RipplerMaskTypeConverter.getInstance(), RipplerMask.RECT) { + @Override + public boolean isSettable(JFXRippler control) { + return control.maskType == null || !control.maskType.isBound(); + } + @Override + public StyleableProperty getStyleableProperty(JFXRippler control) { + return control.maskTypeProperty(); + } + }; + + private static final List> STYLEABLES; + static { + final List> styleables = + new ArrayList>(Parent.getClassCssMetaData()); + Collections.addAll(styleables, + RIPPLER_RECENTER, + RIPPLER_RADIUS, + RIPPLER_FILL, + MASK_TYPE + ); + STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + + @Override + public List> getCssMetaData() { + return getClassCssMetaData(); + } + public static List> getClassCssMetaData() { + return StyleableProperties.STYLEABLES; + } + + public Runnable createManualRipple() { + rippler.setGeneratorCenterX(control.getLayoutBounds().getWidth()/2); + rippler.setGeneratorCenterY(control.getLayoutBounds().getHeight()/2); + return rippler.createManualRipple(); + } + +} diff --git a/src/com/jfoenix/controls/JFXScrollPane.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXScrollPane.java similarity index 100% rename from src/com/jfoenix/controls/JFXScrollPane.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXScrollPane.java diff --git a/src/com/jfoenix/controls/JFXSlider.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXSlider.java similarity index 97% rename from src/com/jfoenix/controls/JFXSlider.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXSlider.java index 592ce4be..29977741 100644 --- a/src/com/jfoenix/controls/JFXSlider.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/JFXSlider.java @@ -1,194 +1,194 @@ -/* - * 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 com.jfoenix.converters.IndicatorPositionConverter; -import com.jfoenix.skins.JFXSliderSkin; -import javafx.beans.binding.StringBinding; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.css.*; -import javafx.scene.control.Control; -import javafx.scene.control.Skin; -import javafx.scene.control.Slider; -import javafx.util.Callback; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * JFXSlider is the material design implementation of a slider. - * - * @author Bashir Elias & Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXSlider extends Slider { - - /** - * {@inheritDoc} - */ - public JFXSlider() { - super(0, 100, 50); - initialize(); - } - - /** - * {@inheritDoc} - */ - public JFXSlider(double min, double max, double value) { - super(min, max, value); - initialize(); - } - - /** - * {@inheritDoc} - */ - @Override - protected Skin createDefaultSkin() { - return new JFXSliderSkin(this); - } - - private void initialize() { - getStyleClass().add(DEFAULT_STYLE_CLASS); - } - - public static enum IndicatorPosition { - LEFT, RIGHT - }; - - /*************************************************************************** - * * - * Properties * - * * - **************************************************************************/ - - /** - * String binding factory for the slider value. - * Sets a custom string for the value text (by default, it shows the value rounded to the nearest whole number). - * - *

For example, to have the value displayed as a percentage (assuming the slider has a range of (0, 100)): - *


-	 * JFXSlider mySlider = ...
-	 * mySlider.setValueFactory(slider ->
-	 * 		Bindings.createStringBinding(
-	 * 			() -> ((int) slider.getValue()) + "%",
-	 * 			slider.valueProperty()
-	 * 		)
-	 * );
-	 * 
- * - * NOTE: might be replaced later with a call back to create the animated thumb node - * @param callback a callback to create the string value binding - */ - private ObjectProperty> valueFactory; - public final ObjectProperty> valueFactoryProperty() { - if (valueFactory == null) { - valueFactory = new SimpleObjectProperty>(this, "valueFactory"); - } - return valueFactory; - } - /** - * @return the current slider value factory - */ - public final Callback getValueFactory() { - return valueFactory == null ? null : valueFactory.get(); - } - /** - * sets custom string binding for the slider text value - * @param valueFactory a callback to create the string value binding - */ - public final void setValueFactory(final Callback valueFactory) { - this.valueFactoryProperty().set(valueFactory); - } - - /*************************************************************************** - * * - * Stylesheet Handling * - * * - **************************************************************************/ - - /** - * Initialize the style class to 'jfx-slider'. - * - * This is the selector class from which CSS can be used to style - * this control. - */ - private static final String DEFAULT_STYLE_CLASS = "jfx-slider"; - - /** - * indicates the position of the slider indicator, can be - * either LEFT or RIGHT - */ - private StyleableObjectProperty indicatorPosition = new SimpleStyleableObjectProperty(StyleableProperties.INDICATOR_POSITION, JFXSlider.this, "indicatorPosition", - IndicatorPosition.LEFT); - - public IndicatorPosition getIndicatorPosition() { - return indicatorPosition == null ? IndicatorPosition.LEFT : indicatorPosition.get(); - } - - public StyleableObjectProperty indicatorPositionProperty() { - return this.indicatorPosition; - } - - public void setIndicatorPosition(IndicatorPosition pos) { - this.indicatorPosition.set(pos); - } - - private static class StyleableProperties { - private static final CssMetaData INDICATOR_POSITION = new CssMetaData("-jfx-indicator-position", IndicatorPositionConverter.getInstance(), - IndicatorPosition.LEFT) { - @Override - public boolean isSettable(JFXSlider control) { - return control.indicatorPosition == null || !control.indicatorPosition.isBound(); - } - - @Override - public StyleableProperty getStyleableProperty(JFXSlider control) { - return control.indicatorPositionProperty(); - } - }; - - private static final List> CHILD_STYLEABLES; - static { - final List> styleables = new ArrayList>(Control.getClassCssMetaData()); - Collections.addAll(styleables, INDICATOR_POSITION); - CHILD_STYLEABLES = Collections.unmodifiableList(styleables); - } - } - - // inherit the styleable properties from parent - private List> STYLEABLES; - - @Override - public List> getControlCssMetaData() { - if (STYLEABLES == null) { - final List> styleables = new ArrayList>(Control.getClassCssMetaData()); - styleables.addAll(getClassCssMetaData()); - styleables.addAll(super.getClassCssMetaData()); - STYLEABLES = Collections.unmodifiableList(styleables); - } - return STYLEABLES; - } - - public static List> getClassCssMetaData() { - return StyleableProperties.CHILD_STYLEABLES; - } -} +/* + * 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 com.jfoenix.converters.IndicatorPositionConverter; +import com.jfoenix.skins.JFXSliderSkin; +import javafx.beans.binding.StringBinding; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.css.*; +import javafx.scene.control.Control; +import javafx.scene.control.Skin; +import javafx.scene.control.Slider; +import javafx.util.Callback; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * JFXSlider is the material design implementation of a slider. + * + * @author Bashir Elias & Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXSlider extends Slider { + + /** + * {@inheritDoc} + */ + public JFXSlider() { + super(0, 100, 50); + initialize(); + } + + /** + * {@inheritDoc} + */ + public JFXSlider(double min, double max, double value) { + super(min, max, value); + initialize(); + } + + /** + * {@inheritDoc} + */ + @Override + protected Skin createDefaultSkin() { + return new JFXSliderSkin(this); + } + + private void initialize() { + getStyleClass().add(DEFAULT_STYLE_CLASS); + } + + public static enum IndicatorPosition { + LEFT, RIGHT + }; + + /*************************************************************************** + * * + * Properties * + * * + **************************************************************************/ + + /** + * String binding factory for the slider value. + * Sets a custom string for the value text (by default, it shows the value rounded to the nearest whole number). + * + *

For example, to have the value displayed as a percentage (assuming the slider has a range of (0, 100)): + *


+	 * JFXSlider mySlider = ...
+	 * mySlider.setValueFactory(slider ->
+	 * 		Bindings.createStringBinding(
+	 * 			() -> ((int) slider.getValue()) + "%",
+	 * 			slider.valueProperty()
+	 * 		)
+	 * );
+	 * 
+ * + * NOTE: might be replaced later with a call back to create the animated thumb node + * @param callback a callback to create the string value binding + */ + private ObjectProperty> valueFactory; + public final ObjectProperty> valueFactoryProperty() { + if (valueFactory == null) { + valueFactory = new SimpleObjectProperty>(this, "valueFactory"); + } + return valueFactory; + } + /** + * @return the current slider value factory + */ + public final Callback getValueFactory() { + return valueFactory == null ? null : valueFactory.get(); + } + /** + * sets custom string binding for the slider text value + * @param valueFactory a callback to create the string value binding + */ + public final void setValueFactory(final Callback valueFactory) { + this.valueFactoryProperty().set(valueFactory); + } + + /*************************************************************************** + * * + * Stylesheet Handling * + * * + **************************************************************************/ + + /** + * Initialize the style class to 'jfx-slider'. + * + * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-slider"; + + /** + * indicates the position of the slider indicator, can be + * either LEFT or RIGHT + */ + private StyleableObjectProperty indicatorPosition = new SimpleStyleableObjectProperty(StyleableProperties.INDICATOR_POSITION, JFXSlider.this, "indicatorPosition", + IndicatorPosition.LEFT); + + public IndicatorPosition getIndicatorPosition() { + return indicatorPosition == null ? IndicatorPosition.LEFT : indicatorPosition.get(); + } + + public StyleableObjectProperty indicatorPositionProperty() { + return this.indicatorPosition; + } + + public void setIndicatorPosition(IndicatorPosition pos) { + this.indicatorPosition.set(pos); + } + + private static class StyleableProperties { + private static final CssMetaData INDICATOR_POSITION = new CssMetaData("-jfx-indicator-position", IndicatorPositionConverter.getInstance(), + IndicatorPosition.LEFT) { + @Override + public boolean isSettable(JFXSlider control) { + return control.indicatorPosition == null || !control.indicatorPosition.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXSlider control) { + return control.indicatorPositionProperty(); + } + }; + + private static final List> CHILD_STYLEABLES; + static { + final List> styleables = new ArrayList>(Control.getClassCssMetaData()); + Collections.addAll(styleables, INDICATOR_POSITION); + CHILD_STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + // inherit the styleable properties from parent + private List> STYLEABLES; + + @Override + public List> getControlCssMetaData() { + if (STYLEABLES == null) { + final List> styleables = new ArrayList>(Control.getClassCssMetaData()); + styleables.addAll(getClassCssMetaData()); + styleables.addAll(super.getClassCssMetaData()); + STYLEABLES = Collections.unmodifiableList(styleables); + } + return STYLEABLES; + } + + public static List> getClassCssMetaData() { + return StyleableProperties.CHILD_STYLEABLES; + } +} diff --git a/src/com/jfoenix/controls/JFXSnackbar.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXSnackbar.java similarity index 97% rename from src/com/jfoenix/controls/JFXSnackbar.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXSnackbar.java index 07e132a4..a922aaa5 100644 --- a/src/com/jfoenix/controls/JFXSnackbar.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/JFXSnackbar.java @@ -1,370 +1,370 @@ -/* - * 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 com.jfoenix.controls.JFXButton.ButtonType; -import javafx.animation.Interpolator; -import javafx.animation.KeyFrame; -import javafx.animation.KeyValue; -import javafx.animation.Timeline; -import javafx.application.Platform; -import javafx.beans.binding.Bindings; -import javafx.beans.value.ChangeListener; -import javafx.event.Event; -import javafx.event.EventHandler; -import javafx.event.EventType; -import javafx.geometry.Bounds; -import javafx.geometry.Insets; -import javafx.scene.Group; -import javafx.scene.control.Control; -import javafx.scene.control.Label; -import javafx.scene.input.MouseEvent; -import javafx.scene.layout.BorderPane; -import javafx.scene.layout.Pane; -import javafx.scene.layout.StackPane; -import javafx.util.Duration; - -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * - * @see - * Snackbars & toasts - * - * The use of a javafx Popup or PopupContainer for notifications would seem intuitive but Popups are - * displayed in their own dedicated windows and alligning the popup window and handling window on top - * layering is more trouble then it is worth. - * - */ -public class JFXSnackbar extends StackPane { - - private Label toast; - private JFXButton action; - - private Pane snackbarContainer; - private BorderPane content; - private Group popup; - private ChangeListener sizeListener; - - private AtomicBoolean processingQueue = new AtomicBoolean(false); - private ConcurrentLinkedQueue eventQueue = new ConcurrentLinkedQueue (); - private StackPane actionContainer; - - Interpolator easeInterpolator = Interpolator.SPLINE(0.250, 0.100, 0.250, 1.000); - - public JFXSnackbar() { - this(null); - } - - public JFXSnackbar(Pane snackbarContainer) { - - BorderPane bPane = new BorderPane(); - toast = new Label(); - toast.setMinWidth(Control.USE_PREF_SIZE); - toast.getStyleClass().add("jfx-snackbar-toast"); - toast.setWrapText(true); - StackPane toastContainer = new StackPane(toast); - toastContainer.setPadding(new Insets(20)); - bPane.setLeft(toastContainer); - - action = new JFXButton(); - action.setMinWidth(Control.USE_PREF_SIZE); - action.setButtonType(ButtonType.FLAT); - action.getStyleClass().add("jfx-snackbar-action"); - - // actions will be added upon showing the snackbar if needed - actionContainer = new StackPane(action); - actionContainer.setPadding(new Insets(0,10,0,0)); - bPane.setRight(actionContainer); - - toast.prefWidthProperty().bind(Bindings.createDoubleBinding(()->{ - if(this.getPrefWidth() == -1) return this.getPrefWidth(); - double actionWidth = actionContainer.isVisible()?actionContainer.getWidth():0.0; - return this.prefWidthProperty().get() - actionWidth; - }, this.prefWidthProperty(), actionContainer.widthProperty(), actionContainer.visibleProperty())); - - //bind the content's height and width from this snackbar allowing the content's dimensions to be set externally - bPane.prefWidthProperty().bind(this.prefWidthProperty()); - - content = bPane; - - content.getStyleClass().add("jfx-snackbar-content"); - //setting a shadow enlarges the snackbar height leaving a gap below it - //JFXDepthManager.setDepth(content, 4); - - //wrap the content in a group so that the content is managed inside its own container - //but the group is not managed in the snackbarContainer so it does not affect any layout calculations - popup= new Group(); - popup.getChildren().add(content); - popup.setManaged(false); - popup.setVisible(false); - - sizeListener = (o, oldVal, newVal) ->{refreshPopup();}; - - // register the container before resizing it - registerSnackbarContainer(snackbarContainer); - - // resize the popup if its layout has been changed - popup.layoutBoundsProperty().addListener((o,oldVal,newVal)-> refreshPopup()); - - addEventHandler(SnackbarEvent.SNACKBAR, (e)-> enqueue(e)); - - //This control actually orchestrates the popup logic and is never visibly displayed. - this.setVisible(false); - this.setManaged(false); - - } - - /*************************************************************************** - * * Setters / Getters * * - **************************************************************************/ - - public Pane getPopupContainer() { - return snackbarContainer; - } - - - /*************************************************************************** - * * Public API * * - **************************************************************************/ - - public void registerSnackbarContainer(Pane snackbarContainer) { - - if (snackbarContainer != null) { - if (this.snackbarContainer!=null){ - //since listeners are added the container should be properly registered/unregistered - throw new IllegalArgumentException("Snackbar Container already set"); - } - - this.snackbarContainer = snackbarContainer; - this.snackbarContainer.getChildren().add(popup); - this.snackbarContainer.heightProperty().addListener(sizeListener); - this.snackbarContainer.widthProperty().addListener(sizeListener); - } - - } - - public void unregisterSnackbarContainer(Pane snackbarContainer) { - - if (snackbarContainer != null) { - if (this.snackbarContainer==null){ - throw new IllegalArgumentException("Snackbar Container not set"); - } - - this.snackbarContainer.getChildren().remove(popup); - this.snackbarContainer.heightProperty().removeListener(sizeListener); - this.snackbarContainer.widthProperty().removeListener(sizeListener); - this.snackbarContainer = null; - } - } - - public void show(String toastMessage, long timeout) { - this.show(toastMessage, null,timeout, null); - } - public void show(String message, String actionText, EventHandler actionHandler) { - this.show(message, actionText,-1, actionHandler); - } - public void show(String message, String actionText, long timeout, EventHandler actionHandler) { - toast.setText(message); - if (actionText != null && !actionText.isEmpty()) { - action.setVisible(true); - actionContainer.setVisible(true); - actionContainer.setManaged(true); - // to force updating the layout bounds - action.setText(""); - action.setText(actionText); - action.setOnMouseClicked(actionHandler); - } else { - actionContainer.setVisible(false); - actionContainer.setManaged(false); - action.setVisible(false); - } - Timeline animation = getTimeline(timeout); - animation.play(); - } - - - private Timeline getTimeline(long timeout) { - Timeline animation; - if(timeout <= 0){ - animation = new Timeline( - new KeyFrame( - Duration.ZERO, - (e)->popup.toBack(), - new KeyValue(popup.visibleProperty(), false ,Interpolator.EASE_BOTH), - new KeyValue(popup.translateYProperty(), popup.getLayoutBounds().getHeight(), easeInterpolator), - new KeyValue(popup.opacityProperty(), 0 , easeInterpolator) - ), - new KeyFrame( - Duration.millis(10), - (e)->popup.toFront(), - new KeyValue(popup.visibleProperty(), true ,Interpolator.EASE_BOTH) - ), - new KeyFrame(Duration.millis(300), - new KeyValue(popup.opacityProperty(), 1 , easeInterpolator), - new KeyValue(popup.translateYProperty(), 0, easeInterpolator) - ) - ); - animation.setCycleCount(1); - }else { - animation = new Timeline( - new KeyFrame( - Duration.ZERO, - (e) -> popup.toBack(), - new KeyValue(popup.visibleProperty(), false, Interpolator.EASE_BOTH), - new KeyValue(popup.translateYProperty(), popup.getLayoutBounds().getHeight(), easeInterpolator), - new KeyValue(popup.opacityProperty(), 0, easeInterpolator) - ), - new KeyFrame( - Duration.millis(10), - (e) -> popup.toFront(), - new KeyValue(popup.visibleProperty(), true, Interpolator.EASE_BOTH) - ), - new KeyFrame(Duration.millis(300), - new KeyValue(popup.opacityProperty(), 1, easeInterpolator), - new KeyValue(popup.translateYProperty(), 0, easeInterpolator) - ), - new KeyFrame(Duration.millis(timeout / 2)) - ); - animation.setAutoReverse(true); - animation.setCycleCount(2); - animation.setOnFinished((e) -> processSnackbars()); - } - return animation; - } - - public void close(){ - Timeline animation = new Timeline( - new KeyFrame( - Duration.ZERO, - (e)->popup.toFront(), - new KeyValue(popup.opacityProperty(), 1 , easeInterpolator), - new KeyValue(popup.translateYProperty(), 0, easeInterpolator) - ), - new KeyFrame( - Duration.millis(290), - new KeyValue(popup.visibleProperty(), true ,Interpolator.EASE_BOTH) - ), - new KeyFrame(Duration.millis(300), - (e)->popup.toBack(), - new KeyValue(popup.visibleProperty(), false ,Interpolator.EASE_BOTH), - new KeyValue(popup.translateYProperty(), popup.getLayoutBounds().getHeight(), easeInterpolator), - new KeyValue(popup.opacityProperty(), 0 , easeInterpolator) - ) - ); - animation.setCycleCount(1); - animation.setOnFinished((e)-> processSnackbars()); - animation.play(); - } - - private void processSnackbars() { - SnackbarEvent qevent = eventQueue.poll(); - if (qevent != null) { - if(qevent.isPersistent()) show(qevent.getMessage(), qevent.getActionText(), qevent.getActionHandler()); - else show(qevent.getMessage(), qevent.getActionText(), qevent.getTimeout(), qevent.getActionHandler()); - } else { - //The enqueue method and this listener should be executed sequentially on the FX Thread so there - //should not be a race condition - processingQueue.getAndSet(false); - } - } - - - - public void refreshPopup(){ - Bounds contentBound = popup.getLayoutBounds(); - double offsetX = Math.ceil((snackbarContainer.getWidth()/2)) - Math.ceil((contentBound.getWidth()/2)) ; - double offsetY = snackbarContainer.getHeight()-contentBound.getHeight(); - popup.setLayoutX(offsetX); - popup.setLayoutY(offsetY); - - } - - public void enqueue(SnackbarEvent event) { - eventQueue.add(event); - if (processingQueue.compareAndSet(false, true)){ - Platform.runLater(() -> { - SnackbarEvent qevent = eventQueue.poll(); - if (qevent != null) { - if(qevent.isPersistent()) show(qevent.getMessage(), qevent.getActionText(), qevent.getActionHandler()); - else show(qevent.getMessage(), qevent.getActionText(), qevent.getTimeout(), qevent.getActionHandler()); - } - }); - } - } - - - /*************************************************************************** - * * Event API * * - **************************************************************************/ - - public static class SnackbarEvent extends Event { - - private final String message; - private final String actionText; - private final long timeout; - private final boolean persistent; - private final EventHandler actionHandler; - - - public String getMessage() { - return message; - } - - public String getActionText() { - return actionText; - } - - public long getTimeout() { - return timeout; - } - - public EventHandler getActionHandler() { - return actionHandler; - } - - public static final EventType SNACKBAR = new EventType<>(Event.ANY, "SNACKBAR"); - - public SnackbarEvent(String message) { - this(message,null,3000, false,null); - - } - - public SnackbarEvent(String message, String actionText, long timeout, boolean persistent, EventHandler actionHandler) { - super(SNACKBAR); - this.message=message; - this.actionText=actionText; - this.timeout=timeout < 1 ? 3000:timeout; - this.actionHandler=actionHandler; - this.persistent = persistent; - } - - @Override - public EventType getEventType() { - return (EventType) super.getEventType(); - } - - public boolean isPersistent() { - return persistent; - } - } -} - +/* + * 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 com.jfoenix.controls.JFXButton.ButtonType; +import javafx.animation.Interpolator; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.application.Platform; +import javafx.beans.binding.Bindings; +import javafx.beans.value.ChangeListener; +import javafx.event.Event; +import javafx.event.EventHandler; +import javafx.event.EventType; +import javafx.geometry.Bounds; +import javafx.geometry.Insets; +import javafx.scene.Group; +import javafx.scene.control.Control; +import javafx.scene.control.Label; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.Pane; +import javafx.scene.layout.StackPane; +import javafx.util.Duration; + +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * + * @see + * Snackbars & toasts + * + * The use of a javafx Popup or PopupContainer for notifications would seem intuitive but Popups are + * displayed in their own dedicated windows and alligning the popup window and handling window on top + * layering is more trouble then it is worth. + * + */ +public class JFXSnackbar extends StackPane { + + private Label toast; + private JFXButton action; + + private Pane snackbarContainer; + private BorderPane content; + private Group popup; + private ChangeListener sizeListener; + + private AtomicBoolean processingQueue = new AtomicBoolean(false); + private ConcurrentLinkedQueue eventQueue = new ConcurrentLinkedQueue (); + private StackPane actionContainer; + + Interpolator easeInterpolator = Interpolator.SPLINE(0.250, 0.100, 0.250, 1.000); + + public JFXSnackbar() { + this(null); + } + + public JFXSnackbar(Pane snackbarContainer) { + + BorderPane bPane = new BorderPane(); + toast = new Label(); + toast.setMinWidth(Control.USE_PREF_SIZE); + toast.getStyleClass().add("jfx-snackbar-toast"); + toast.setWrapText(true); + StackPane toastContainer = new StackPane(toast); + toastContainer.setPadding(new Insets(20)); + bPane.setLeft(toastContainer); + + action = new JFXButton(); + action.setMinWidth(Control.USE_PREF_SIZE); + action.setButtonType(ButtonType.FLAT); + action.getStyleClass().add("jfx-snackbar-action"); + + // actions will be added upon showing the snackbar if needed + actionContainer = new StackPane(action); + actionContainer.setPadding(new Insets(0,10,0,0)); + bPane.setRight(actionContainer); + + toast.prefWidthProperty().bind(Bindings.createDoubleBinding(()->{ + if(this.getPrefWidth() == -1) return this.getPrefWidth(); + double actionWidth = actionContainer.isVisible()?actionContainer.getWidth():0.0; + return this.prefWidthProperty().get() - actionWidth; + }, this.prefWidthProperty(), actionContainer.widthProperty(), actionContainer.visibleProperty())); + + //bind the content's height and width from this snackbar allowing the content's dimensions to be set externally + bPane.prefWidthProperty().bind(this.prefWidthProperty()); + + content = bPane; + + content.getStyleClass().add("jfx-snackbar-content"); + //setting a shadow enlarges the snackbar height leaving a gap below it + //JFXDepthManager.setDepth(content, 4); + + //wrap the content in a group so that the content is managed inside its own container + //but the group is not managed in the snackbarContainer so it does not affect any layout calculations + popup= new Group(); + popup.getChildren().add(content); + popup.setManaged(false); + popup.setVisible(false); + + sizeListener = (o, oldVal, newVal) ->{refreshPopup();}; + + // register the container before resizing it + registerSnackbarContainer(snackbarContainer); + + // resize the popup if its layout has been changed + popup.layoutBoundsProperty().addListener((o,oldVal,newVal)-> refreshPopup()); + + addEventHandler(SnackbarEvent.SNACKBAR, (e)-> enqueue(e)); + + //This control actually orchestrates the popup logic and is never visibly displayed. + this.setVisible(false); + this.setManaged(false); + + } + + /*************************************************************************** + * * Setters / Getters * * + **************************************************************************/ + + public Pane getPopupContainer() { + return snackbarContainer; + } + + + /*************************************************************************** + * * Public API * * + **************************************************************************/ + + public void registerSnackbarContainer(Pane snackbarContainer) { + + if (snackbarContainer != null) { + if (this.snackbarContainer!=null){ + //since listeners are added the container should be properly registered/unregistered + throw new IllegalArgumentException("Snackbar Container already set"); + } + + this.snackbarContainer = snackbarContainer; + this.snackbarContainer.getChildren().add(popup); + this.snackbarContainer.heightProperty().addListener(sizeListener); + this.snackbarContainer.widthProperty().addListener(sizeListener); + } + + } + + public void unregisterSnackbarContainer(Pane snackbarContainer) { + + if (snackbarContainer != null) { + if (this.snackbarContainer==null){ + throw new IllegalArgumentException("Snackbar Container not set"); + } + + this.snackbarContainer.getChildren().remove(popup); + this.snackbarContainer.heightProperty().removeListener(sizeListener); + this.snackbarContainer.widthProperty().removeListener(sizeListener); + this.snackbarContainer = null; + } + } + + public void show(String toastMessage, long timeout) { + this.show(toastMessage, null,timeout, null); + } + public void show(String message, String actionText, EventHandler actionHandler) { + this.show(message, actionText,-1, actionHandler); + } + public void show(String message, String actionText, long timeout, EventHandler actionHandler) { + toast.setText(message); + if (actionText != null && !actionText.isEmpty()) { + action.setVisible(true); + actionContainer.setVisible(true); + actionContainer.setManaged(true); + // to force updating the layout bounds + action.setText(""); + action.setText(actionText); + action.setOnMouseClicked(actionHandler); + } else { + actionContainer.setVisible(false); + actionContainer.setManaged(false); + action.setVisible(false); + } + Timeline animation = getTimeline(timeout); + animation.play(); + } + + + private Timeline getTimeline(long timeout) { + Timeline animation; + if(timeout <= 0){ + animation = new Timeline( + new KeyFrame( + Duration.ZERO, + (e)->popup.toBack(), + new KeyValue(popup.visibleProperty(), false ,Interpolator.EASE_BOTH), + new KeyValue(popup.translateYProperty(), popup.getLayoutBounds().getHeight(), easeInterpolator), + new KeyValue(popup.opacityProperty(), 0 , easeInterpolator) + ), + new KeyFrame( + Duration.millis(10), + (e)->popup.toFront(), + new KeyValue(popup.visibleProperty(), true ,Interpolator.EASE_BOTH) + ), + new KeyFrame(Duration.millis(300), + new KeyValue(popup.opacityProperty(), 1 , easeInterpolator), + new KeyValue(popup.translateYProperty(), 0, easeInterpolator) + ) + ); + animation.setCycleCount(1); + }else { + animation = new Timeline( + new KeyFrame( + Duration.ZERO, + (e) -> popup.toBack(), + new KeyValue(popup.visibleProperty(), false, Interpolator.EASE_BOTH), + new KeyValue(popup.translateYProperty(), popup.getLayoutBounds().getHeight(), easeInterpolator), + new KeyValue(popup.opacityProperty(), 0, easeInterpolator) + ), + new KeyFrame( + Duration.millis(10), + (e) -> popup.toFront(), + new KeyValue(popup.visibleProperty(), true, Interpolator.EASE_BOTH) + ), + new KeyFrame(Duration.millis(300), + new KeyValue(popup.opacityProperty(), 1, easeInterpolator), + new KeyValue(popup.translateYProperty(), 0, easeInterpolator) + ), + new KeyFrame(Duration.millis(timeout / 2)) + ); + animation.setAutoReverse(true); + animation.setCycleCount(2); + animation.setOnFinished((e) -> processSnackbars()); + } + return animation; + } + + public void close(){ + Timeline animation = new Timeline( + new KeyFrame( + Duration.ZERO, + (e)->popup.toFront(), + new KeyValue(popup.opacityProperty(), 1 , easeInterpolator), + new KeyValue(popup.translateYProperty(), 0, easeInterpolator) + ), + new KeyFrame( + Duration.millis(290), + new KeyValue(popup.visibleProperty(), true ,Interpolator.EASE_BOTH) + ), + new KeyFrame(Duration.millis(300), + (e)->popup.toBack(), + new KeyValue(popup.visibleProperty(), false ,Interpolator.EASE_BOTH), + new KeyValue(popup.translateYProperty(), popup.getLayoutBounds().getHeight(), easeInterpolator), + new KeyValue(popup.opacityProperty(), 0 , easeInterpolator) + ) + ); + animation.setCycleCount(1); + animation.setOnFinished((e)-> processSnackbars()); + animation.play(); + } + + private void processSnackbars() { + SnackbarEvent qevent = eventQueue.poll(); + if (qevent != null) { + if(qevent.isPersistent()) show(qevent.getMessage(), qevent.getActionText(), qevent.getActionHandler()); + else show(qevent.getMessage(), qevent.getActionText(), qevent.getTimeout(), qevent.getActionHandler()); + } else { + //The enqueue method and this listener should be executed sequentially on the FX Thread so there + //should not be a race condition + processingQueue.getAndSet(false); + } + } + + + + public void refreshPopup(){ + Bounds contentBound = popup.getLayoutBounds(); + double offsetX = Math.ceil((snackbarContainer.getWidth()/2)) - Math.ceil((contentBound.getWidth()/2)) ; + double offsetY = snackbarContainer.getHeight()-contentBound.getHeight(); + popup.setLayoutX(offsetX); + popup.setLayoutY(offsetY); + + } + + public void enqueue(SnackbarEvent event) { + eventQueue.add(event); + if (processingQueue.compareAndSet(false, true)){ + Platform.runLater(() -> { + SnackbarEvent qevent = eventQueue.poll(); + if (qevent != null) { + if(qevent.isPersistent()) show(qevent.getMessage(), qevent.getActionText(), qevent.getActionHandler()); + else show(qevent.getMessage(), qevent.getActionText(), qevent.getTimeout(), qevent.getActionHandler()); + } + }); + } + } + + + /*************************************************************************** + * * Event API * * + **************************************************************************/ + + public static class SnackbarEvent extends Event { + + private final String message; + private final String actionText; + private final long timeout; + private final boolean persistent; + private final EventHandler actionHandler; + + + public String getMessage() { + return message; + } + + public String getActionText() { + return actionText; + } + + public long getTimeout() { + return timeout; + } + + public EventHandler getActionHandler() { + return actionHandler; + } + + public static final EventType SNACKBAR = new EventType<>(Event.ANY, "SNACKBAR"); + + public SnackbarEvent(String message) { + this(message,null,3000, false,null); + + } + + public SnackbarEvent(String message, String actionText, long timeout, boolean persistent, EventHandler actionHandler) { + super(SNACKBAR); + this.message=message; + this.actionText=actionText; + this.timeout=timeout < 1 ? 3000:timeout; + this.actionHandler=actionHandler; + this.persistent = persistent; + } + + @Override + public EventType getEventType() { + return (EventType) super.getEventType(); + } + + public boolean isPersistent() { + return persistent; + } + } +} + diff --git a/src/com/jfoenix/controls/JFXSpinner.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXSpinner.java similarity index 97% rename from src/com/jfoenix/controls/JFXSpinner.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXSpinner.java index 5584a3d9..a30b63b7 100644 --- a/src/com/jfoenix/controls/JFXSpinner.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/JFXSpinner.java @@ -1,244 +1,244 @@ -/* - * 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 com.sun.javafx.css.converters.SizeConverter; -import javafx.animation.Interpolator; -import javafx.animation.KeyFrame; -import javafx.animation.KeyValue; -import javafx.animation.Timeline; -import javafx.beans.binding.Bindings; -import javafx.css.CssMetaData; -import javafx.css.SimpleStyleableDoubleProperty; -import javafx.css.Styleable; -import javafx.css.StyleableDoubleProperty; -import javafx.scene.Parent; -import javafx.scene.layout.StackPane; -import javafx.scene.paint.Color; -import javafx.scene.shape.Arc; -import javafx.util.Duration; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * JFXSpinner is the material design implementation of a loading spinner. - * - * @author Bashir Elias & Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXSpinner extends StackPane { - - private Color greenColor, redColor, yellowColor, blueColor, initialColor; - private Timeline timeline; - private Arc arc; - private boolean initialized; - - /** - * creates a spinner node - */ - public JFXSpinner() { - super(); - getStyleClass().add(DEFAULT_STYLE_CLASS); - initialize(); - } - - private void initialize() { - blueColor = Color.valueOf("#4285f4"); - redColor = Color.valueOf("#db4437"); - yellowColor = Color.valueOf("#f4b400"); - greenColor = Color.valueOf("#0F9D58"); - - arc = new Arc(0, 0, 12, 12, 0, 360); - arc.setFill(Color.TRANSPARENT); - arc.setStrokeWidth(3); - arc.getStyleClass().addAll("arc"); - arc.radiusXProperty().bindBidirectional(radius); - arc.radiusYProperty().bindBidirectional(radius); - getChildren().add(arc); - - this.minWidthProperty().bind(Bindings.createDoubleBinding(()->{ - return getRadius()*2 + arc.getStrokeWidth() + 5; - }, radius,arc.strokeWidthProperty())); - - this.maxWidthProperty().bind(Bindings.createDoubleBinding(()->{ - return getRadius()*2 + arc.getStrokeWidth() + 5; - }, radius,arc.strokeWidthProperty())); - - this.minHeightProperty().bind(Bindings.createDoubleBinding(()->{ - return getRadius()*2 + arc.getStrokeWidth() + 5; - }, radius,arc.strokeWidthProperty())); - - this.maxHeightProperty().bind(Bindings.createDoubleBinding(()->{ - return getRadius()*2 + arc.getStrokeWidth() + 5; - }, radius,arc.strokeWidthProperty())); - - } - - private KeyFrame[] getKeyFrames(double angle, double duration, Color color) { - KeyFrame[] frames = new KeyFrame[4]; - frames[0] = new KeyFrame(Duration.seconds(duration), new KeyValue(arc.lengthProperty(), 5, Interpolator.LINEAR), new KeyValue(arc.startAngleProperty(), angle + 45 + getStartingAngle(), Interpolator.LINEAR)); - frames[1] = new KeyFrame(Duration.seconds(duration + 0.4), new KeyValue(arc.lengthProperty(), 250, Interpolator.LINEAR), new KeyValue(arc.startAngleProperty(), angle + 90 + getStartingAngle(), Interpolator.LINEAR)); - frames[2] = new KeyFrame(Duration.seconds(duration + 0.7), new KeyValue(arc.lengthProperty(), 250, Interpolator.LINEAR), new KeyValue(arc.startAngleProperty(), angle + 135 + getStartingAngle(), Interpolator.LINEAR)); - frames[3] = new KeyFrame(Duration.seconds(duration + 1.1), new KeyValue(arc.lengthProperty(), 5, Interpolator.LINEAR), new KeyValue(arc.startAngleProperty(), angle + 435 + getStartingAngle(), Interpolator.LINEAR), - new KeyValue(arc.strokeProperty(), color, Interpolator.EASE_BOTH)); - return frames; - } - - /** - * {@inheritDoc} - */ - @Override protected void layoutChildren() { - if (!initialized) { - super.layoutChildren(); - initialColor = (Color) arc.getStroke(); - if (initialColor == null) { - arc.setStroke(blueColor); - } - - KeyFrame[] blueFrame = getKeyFrames( 0, 0, initialColor == null ? blueColor : initialColor); - KeyFrame[] redFrame = getKeyFrames( 450, 1.4, initialColor == null ? redColor : initialColor); - KeyFrame[] yellowFrame = getKeyFrames( 900, 2.8, initialColor == null ? yellowColor : initialColor); - KeyFrame[] greenFrame = getKeyFrames( 1350, 4.2, initialColor == null ? greenColor : initialColor); - - KeyFrame endingFrame = new KeyFrame(Duration.seconds(5.6), new KeyValue(arc.lengthProperty(), 5, Interpolator.LINEAR), new KeyValue(arc.startAngleProperty(), 1845 + getStartingAngle(), Interpolator.LINEAR)); - - if(timeline!=null) timeline.stop(); - timeline = new Timeline(blueFrame[0], blueFrame[1], blueFrame[2], blueFrame[3], redFrame[0], redFrame[1], redFrame[2], redFrame[3], yellowFrame[0], yellowFrame[1], yellowFrame[2], yellowFrame[3], - greenFrame[0], greenFrame[1], greenFrame[2], greenFrame[3], endingFrame); - timeline.setCycleCount(Timeline.INDEFINITE); - timeline.setRate(1); - timeline.play(); - - initialized = true; - } - } - - - /*************************************************************************** - * * - * Stylesheet Handling * - * * - **************************************************************************/ - - /** - * Initialize the style class to 'jfx-spinner'. - * - * This is the selector class from which CSS can be used to style - * this control. - */ - private static final String DEFAULT_STYLE_CLASS = "jfx-spinner"; - - - /** - * specifies the radius of the spinner node - */ - private StyleableDoubleProperty radius = new SimpleStyleableDoubleProperty(StyleableProperties.RADIUS, JFXSpinner.this, "radius", 12.0); - - public final StyleableDoubleProperty radiusProperty() { - return this.radius; - } - - public final double getRadius() { - return this.radiusProperty().get(); - } - - public final void setRadius(final double radius) { - this.radiusProperty().set(radius); - } - - /** - * specifies from which angle the spinner should start spinning - */ - private StyleableDoubleProperty startingAngle = new SimpleStyleableDoubleProperty(StyleableProperties.STARTING_ANGLE, JFXSpinner.this, "starting_angle", 360 - Math.random()*720); - - public final StyleableDoubleProperty startingAngleProperty() { - return this.startingAngle; - } - - public final double getStartingAngle() { - return this.startingAngleProperty().get(); - } - - public final void setStartingAngle(final double startingAngle) { - this.startingAngleProperty().set(startingAngle); - } - - private static class StyleableProperties { - private static final CssMetaData RADIUS = - new CssMetaData< JFXSpinner, Number>("-jfx-radius", - SizeConverter.getInstance(), 12) { - @Override - public boolean isSettable(JFXSpinner control) { - return control.radius == null || !control.radius.isBound(); - } - @Override - public StyleableDoubleProperty getStyleableProperty(JFXSpinner control) { - return control.radius; - } - }; - - private static final CssMetaData STARTING_ANGLE = - new CssMetaData< JFXSpinner, Number>("-jfx-starting-angle", - SizeConverter.getInstance(), 360 - Math.random()*720) { - @Override - public boolean isSettable(JFXSpinner control) { - return control.startingAngle == null || !control.startingAngle.isBound(); - } - @Override - public StyleableDoubleProperty getStyleableProperty(JFXSpinner control) { - return control.startingAngle; - } - }; - - - private static final List> CHILD_STYLEABLES; - static { - final List> styleables = - new ArrayList>(Parent.getClassCssMetaData()); - Collections.addAll(styleables, - RADIUS, - STARTING_ANGLE - ); - CHILD_STYLEABLES = Collections.unmodifiableList(styleables); - } - } - - // inherit the styleable properties from parent - private List> STYLEABLES; - - @Override - public List> getCssMetaData() { - if(STYLEABLES == null){ - final List> styleables = - new ArrayList>(Parent.getClassCssMetaData()); - styleables.addAll(getClassCssMetaData()); - styleables.addAll(super.getClassCssMetaData()); - STYLEABLES = Collections.unmodifiableList(styleables); - } - return STYLEABLES; - } - public static List> getClassCssMetaData() { - return StyleableProperties.CHILD_STYLEABLES; - } - - -} +/* + * 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 com.sun.javafx.css.converters.SizeConverter; +import javafx.animation.Interpolator; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.beans.binding.Bindings; +import javafx.css.CssMetaData; +import javafx.css.SimpleStyleableDoubleProperty; +import javafx.css.Styleable; +import javafx.css.StyleableDoubleProperty; +import javafx.scene.Parent; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; +import javafx.scene.shape.Arc; +import javafx.util.Duration; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * JFXSpinner is the material design implementation of a loading spinner. + * + * @author Bashir Elias & Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXSpinner extends StackPane { + + private Color greenColor, redColor, yellowColor, blueColor, initialColor; + private Timeline timeline; + private Arc arc; + private boolean initialized; + + /** + * creates a spinner node + */ + public JFXSpinner() { + super(); + getStyleClass().add(DEFAULT_STYLE_CLASS); + initialize(); + } + + private void initialize() { + blueColor = Color.valueOf("#4285f4"); + redColor = Color.valueOf("#db4437"); + yellowColor = Color.valueOf("#f4b400"); + greenColor = Color.valueOf("#0F9D58"); + + arc = new Arc(0, 0, 12, 12, 0, 360); + arc.setFill(Color.TRANSPARENT); + arc.setStrokeWidth(3); + arc.getStyleClass().addAll("arc"); + arc.radiusXProperty().bindBidirectional(radius); + arc.radiusYProperty().bindBidirectional(radius); + getChildren().add(arc); + + this.minWidthProperty().bind(Bindings.createDoubleBinding(()->{ + return getRadius()*2 + arc.getStrokeWidth() + 5; + }, radius,arc.strokeWidthProperty())); + + this.maxWidthProperty().bind(Bindings.createDoubleBinding(()->{ + return getRadius()*2 + arc.getStrokeWidth() + 5; + }, radius,arc.strokeWidthProperty())); + + this.minHeightProperty().bind(Bindings.createDoubleBinding(()->{ + return getRadius()*2 + arc.getStrokeWidth() + 5; + }, radius,arc.strokeWidthProperty())); + + this.maxHeightProperty().bind(Bindings.createDoubleBinding(()->{ + return getRadius()*2 + arc.getStrokeWidth() + 5; + }, radius,arc.strokeWidthProperty())); + + } + + private KeyFrame[] getKeyFrames(double angle, double duration, Color color) { + KeyFrame[] frames = new KeyFrame[4]; + frames[0] = new KeyFrame(Duration.seconds(duration), new KeyValue(arc.lengthProperty(), 5, Interpolator.LINEAR), new KeyValue(arc.startAngleProperty(), angle + 45 + getStartingAngle(), Interpolator.LINEAR)); + frames[1] = new KeyFrame(Duration.seconds(duration + 0.4), new KeyValue(arc.lengthProperty(), 250, Interpolator.LINEAR), new KeyValue(arc.startAngleProperty(), angle + 90 + getStartingAngle(), Interpolator.LINEAR)); + frames[2] = new KeyFrame(Duration.seconds(duration + 0.7), new KeyValue(arc.lengthProperty(), 250, Interpolator.LINEAR), new KeyValue(arc.startAngleProperty(), angle + 135 + getStartingAngle(), Interpolator.LINEAR)); + frames[3] = new KeyFrame(Duration.seconds(duration + 1.1), new KeyValue(arc.lengthProperty(), 5, Interpolator.LINEAR), new KeyValue(arc.startAngleProperty(), angle + 435 + getStartingAngle(), Interpolator.LINEAR), + new KeyValue(arc.strokeProperty(), color, Interpolator.EASE_BOTH)); + return frames; + } + + /** + * {@inheritDoc} + */ + @Override protected void layoutChildren() { + if (!initialized) { + super.layoutChildren(); + initialColor = (Color) arc.getStroke(); + if (initialColor == null) { + arc.setStroke(blueColor); + } + + KeyFrame[] blueFrame = getKeyFrames( 0, 0, initialColor == null ? blueColor : initialColor); + KeyFrame[] redFrame = getKeyFrames( 450, 1.4, initialColor == null ? redColor : initialColor); + KeyFrame[] yellowFrame = getKeyFrames( 900, 2.8, initialColor == null ? yellowColor : initialColor); + KeyFrame[] greenFrame = getKeyFrames( 1350, 4.2, initialColor == null ? greenColor : initialColor); + + KeyFrame endingFrame = new KeyFrame(Duration.seconds(5.6), new KeyValue(arc.lengthProperty(), 5, Interpolator.LINEAR), new KeyValue(arc.startAngleProperty(), 1845 + getStartingAngle(), Interpolator.LINEAR)); + + if(timeline!=null) timeline.stop(); + timeline = new Timeline(blueFrame[0], blueFrame[1], blueFrame[2], blueFrame[3], redFrame[0], redFrame[1], redFrame[2], redFrame[3], yellowFrame[0], yellowFrame[1], yellowFrame[2], yellowFrame[3], + greenFrame[0], greenFrame[1], greenFrame[2], greenFrame[3], endingFrame); + timeline.setCycleCount(Timeline.INDEFINITE); + timeline.setRate(1); + timeline.play(); + + initialized = true; + } + } + + + /*************************************************************************** + * * + * Stylesheet Handling * + * * + **************************************************************************/ + + /** + * Initialize the style class to 'jfx-spinner'. + * + * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-spinner"; + + + /** + * specifies the radius of the spinner node + */ + private StyleableDoubleProperty radius = new SimpleStyleableDoubleProperty(StyleableProperties.RADIUS, JFXSpinner.this, "radius", 12.0); + + public final StyleableDoubleProperty radiusProperty() { + return this.radius; + } + + public final double getRadius() { + return this.radiusProperty().get(); + } + + public final void setRadius(final double radius) { + this.radiusProperty().set(radius); + } + + /** + * specifies from which angle the spinner should start spinning + */ + private StyleableDoubleProperty startingAngle = new SimpleStyleableDoubleProperty(StyleableProperties.STARTING_ANGLE, JFXSpinner.this, "starting_angle", 360 - Math.random()*720); + + public final StyleableDoubleProperty startingAngleProperty() { + return this.startingAngle; + } + + public final double getStartingAngle() { + return this.startingAngleProperty().get(); + } + + public final void setStartingAngle(final double startingAngle) { + this.startingAngleProperty().set(startingAngle); + } + + private static class StyleableProperties { + private static final CssMetaData RADIUS = + new CssMetaData< JFXSpinner, Number>("-jfx-radius", + SizeConverter.getInstance(), 12) { + @Override + public boolean isSettable(JFXSpinner control) { + return control.radius == null || !control.radius.isBound(); + } + @Override + public StyleableDoubleProperty getStyleableProperty(JFXSpinner control) { + return control.radius; + } + }; + + private static final CssMetaData STARTING_ANGLE = + new CssMetaData< JFXSpinner, Number>("-jfx-starting-angle", + SizeConverter.getInstance(), 360 - Math.random()*720) { + @Override + public boolean isSettable(JFXSpinner control) { + return control.startingAngle == null || !control.startingAngle.isBound(); + } + @Override + public StyleableDoubleProperty getStyleableProperty(JFXSpinner control) { + return control.startingAngle; + } + }; + + + private static final List> CHILD_STYLEABLES; + static { + final List> styleables = + new ArrayList>(Parent.getClassCssMetaData()); + Collections.addAll(styleables, + RADIUS, + STARTING_ANGLE + ); + CHILD_STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + // inherit the styleable properties from parent + private List> STYLEABLES; + + @Override + public List> getCssMetaData() { + if(STYLEABLES == null){ + final List> styleables = + new ArrayList>(Parent.getClassCssMetaData()); + styleables.addAll(getClassCssMetaData()); + styleables.addAll(super.getClassCssMetaData()); + STYLEABLES = Collections.unmodifiableList(styleables); + } + return STYLEABLES; + } + public static List> getClassCssMetaData() { + return StyleableProperties.CHILD_STYLEABLES; + } + + +} diff --git a/src/com/jfoenix/controls/JFXTabPane.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXTabPane.java similarity index 96% rename from src/com/jfoenix/controls/JFXTabPane.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXTabPane.java index cc35e137..b8dbcdd6 100644 --- a/src/com/jfoenix/controls/JFXTabPane.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/JFXTabPane.java @@ -1,72 +1,72 @@ -/* - * 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 com.jfoenix.skins.JFXTabPaneSkin; -import javafx.scene.control.Skin; -import javafx.scene.control.TabPane; -import javafx.scene.input.MouseEvent; - -/** - * JFXTabPane is the material design implementation of a tab pane. - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXTabPane extends TabPane { - - /** - * {@inheritDoc} - */ - public JFXTabPane() { - super(); - initialize(); - } - - /** - * {@inheritDoc} - */ - @Override - protected Skin createDefaultSkin() { - return new JFXTabPaneSkin(this); - } - - private void initialize() { - this.getStyleClass().setAll(DEFAULT_STYLE_CLASS); - } - - /** - * Initialize the style class to 'jfx-tab-pane'. - * - * This is the selector class from which CSS can be used to style - * this control. - */ - private static final String DEFAULT_STYLE_CLASS = "jfx-tab-pane"; - - /** - * propagate any mouse events on the tab pane to its parent - */ - public void propagateMouseEventsToParent(){ - this.addEventHandler(MouseEvent.ANY, (e)->{ - e.consume(); - this.getParent().fireEvent(e); - }); - } -} +/* + * 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 com.jfoenix.skins.JFXTabPaneSkin; +import javafx.scene.control.Skin; +import javafx.scene.control.TabPane; +import javafx.scene.input.MouseEvent; + +/** + * JFXTabPane is the material design implementation of a tab pane. + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXTabPane extends TabPane { + + /** + * {@inheritDoc} + */ + public JFXTabPane() { + super(); + initialize(); + } + + /** + * {@inheritDoc} + */ + @Override + protected Skin createDefaultSkin() { + return new JFXTabPaneSkin(this); + } + + private void initialize() { + this.getStyleClass().setAll(DEFAULT_STYLE_CLASS); + } + + /** + * Initialize the style class to 'jfx-tab-pane'. + * + * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-tab-pane"; + + /** + * propagate any mouse events on the tab pane to its parent + */ + public void propagateMouseEventsToParent(){ + this.addEventHandler(MouseEvent.ANY, (e)->{ + e.consume(); + this.getParent().fireEvent(e); + }); + } +} diff --git a/src/com/jfoenix/controls/JFXTextArea.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXTextArea.java similarity index 97% rename from src/com/jfoenix/controls/JFXTextArea.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXTextArea.java index 0d1871b1..98a6621d 100644 --- a/src/com/jfoenix/controls/JFXTextArea.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/JFXTextArea.java @@ -1,290 +1,290 @@ -/* - * 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 com.jfoenix.skins.JFXTextAreaSkin; -import com.jfoenix.validation.base.ValidatorBase; -import com.sun.javafx.css.converters.BooleanConverter; -import com.sun.javafx.css.converters.PaintConverter; -import javafx.beans.property.ReadOnlyObjectProperty; -import javafx.beans.property.ReadOnlyObjectWrapper; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.css.*; -import javafx.scene.control.Control; -import javafx.scene.control.Skin; -import javafx.scene.control.TextArea; -import javafx.scene.paint.Color; -import javafx.scene.paint.Paint; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * JFXTextArea is the material design implementation of a text area. - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXTextArea extends TextArea{ - - /** - * {@inheritDoc} - */ - public JFXTextArea() { - super(); - initialize(); - } - - /** - * {@inheritDoc} - */ - public JFXTextArea(String text) { - super(text); - initialize(); - } - - /** - * {@inheritDoc} - */ - @Override - protected Skin createDefaultSkin() { - return new JFXTextAreaSkin(this); - } - - private void initialize() { - this.getStyleClass().add(DEFAULT_STYLE_CLASS); - if(System.getProperty("java.vm.name").toLowerCase().equals("dalvik")){ - this.setStyle("-fx-skin: \"com.jfoenix.android.skins.JFXTextAreaSkinAndroid\";"); - } - } - - /** - * Initialize the style class to 'jfx-text-field'. - * - * This is the selector class from which CSS can be used to style - * this control. - */ - private static final String DEFAULT_STYLE_CLASS = "jfx-text-area"; - - /*************************************************************************** - * * - * Properties * - * * - **************************************************************************/ - - /** - * holds the current active validator on the text area in case of validation error - */ - private ReadOnlyObjectWrapper activeValidator = new ReadOnlyObjectWrapper(); - - public ValidatorBase getActiveValidator() { - return activeValidator == null ? null : activeValidator.get(); - } - - public ReadOnlyObjectProperty activeValidatorProperty() { - return this.activeValidator.getReadOnlyProperty(); - } - - /** - * list of validators that will validate the text value upon calling - * {{@link #validate()} - */ - private ObservableList validators = FXCollections.observableArrayList(); - - public ObservableList getValidators() { - return validators; - } - - public void setValidators(ValidatorBase... validators) { - this.validators.addAll(validators); - } - - /** - * validates the text value using the list of validators provided by the user - * {{@link #setValidators(ValidatorBase...)} - * @return true if the value is valid else false - */ - public boolean validate() { - for (ValidatorBase validator : validators) { - if (validator.getSrcControl() == null) - validator.setSrcControl(this); - validator.validate(); - if (validator.getHasErrors()) { - activeValidator.set(validator); - return false; - } - } - activeValidator.set(null); - return true; - } - - public void resetValidation() { - getStyleClass().remove(activeValidator.get() == null? "" : activeValidator.get().getErrorStyleClass()); - pseudoClassStateChanged(ValidatorBase.PSEUDO_CLASS_ERROR, false); - activeValidator.set(null); - } - - /*************************************************************************** - * * - * styleable Properties * - * * - **************************************************************************/ - - /** - * set true to show a float the prompt text when focusing the field - */ - private StyleableBooleanProperty labelFloat = new SimpleStyleableBooleanProperty(StyleableProperties.LABEL_FLOAT, JFXTextArea.this, "lableFloat", false); - - public final StyleableBooleanProperty labelFloatProperty() { - return this.labelFloat; - } - - public final boolean isLabelFloat() { - return this.labelFloatProperty().get(); - } - - public final void setLabelFloat(final boolean labelFloat) { - this.labelFloatProperty().set(labelFloat); - } - - /** - * default color used when the text area is unfocused - */ - private StyleableObjectProperty unFocusColor = new SimpleStyleableObjectProperty(StyleableProperties.UNFOCUS_COLOR, JFXTextArea.this, "unFocusColor", Color.rgb(77, 77, 77)); - - public Paint getUnFocusColor() { - return unFocusColor == null ? Color.rgb(77, 77, 77) : unFocusColor.get(); - } - - public StyleableObjectProperty unFocusColorProperty() { - return this.unFocusColor; - } - - public void setUnFocusColor(Paint color) { - this.unFocusColor.set(color); - } - - /** - * default color used when the text area is focused - */ - private StyleableObjectProperty focusColor = new SimpleStyleableObjectProperty(StyleableProperties.FOCUS_COLOR, JFXTextArea.this, "focusColor", Color.valueOf("#4059A9")); - - public Paint getFocusColor() { - return focusColor == null ? Color.valueOf("#4059A9") : focusColor.get(); - } - - public StyleableObjectProperty focusColorProperty() { - return this.focusColor; - } - - public void setFocusColor(Paint color) { - this.focusColor.set(color); - } - - /** - * disable animation on validation - */ - private StyleableBooleanProperty disableAnimation = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_ANIMATION, JFXTextArea.this, "disableAnimation", false); - public final StyleableBooleanProperty disableAnimationProperty() { - return this.disableAnimation; - } - public final Boolean isDisableAnimation() { - return disableAnimation == null ? false : this.disableAnimationProperty().get(); - } - public final void setDisableAnimation(final Boolean disabled) { - this.disableAnimationProperty().set(disabled); - } - - private static class StyleableProperties { - private static final CssMetaData UNFOCUS_COLOR = new CssMetaData("-jfx-unfocus-color", PaintConverter.getInstance(), Color.rgb(77, 77, 77)) { - @Override - public boolean isSettable(JFXTextArea control) { - return control.unFocusColor == null || !control.unFocusColor.isBound(); - } - - @Override - public StyleableProperty getStyleableProperty(JFXTextArea control) { - return control.unFocusColorProperty(); - } - }; - private static final CssMetaData FOCUS_COLOR = new CssMetaData("-jfx-focus-color", PaintConverter.getInstance(), Color.valueOf("#4059A9")) { - @Override - public boolean isSettable(JFXTextArea control) { - return control.focusColor == null || !control.focusColor.isBound(); - } - - @Override - public StyleableProperty getStyleableProperty(JFXTextArea control) { - return control.focusColorProperty(); - } - }; - private static final CssMetaData LABEL_FLOAT = new CssMetaData("-jfx-label-float", BooleanConverter.getInstance(), false) { - @Override - public boolean isSettable(JFXTextArea control) { - return control.labelFloat == null || !control.labelFloat.isBound(); - } - - @Override - public StyleableBooleanProperty getStyleableProperty(JFXTextArea control) { - return control.labelFloatProperty(); - } - }; - - private static final CssMetaData< JFXTextArea, Boolean> DISABLE_ANIMATION = - new CssMetaData< JFXTextArea, Boolean>("-jfx-disable-animation", - BooleanConverter.getInstance(), false) { - @Override - public boolean isSettable(JFXTextArea control) { - return control.disableAnimation == null || !control.disableAnimation.isBound(); - } - @Override - public StyleableBooleanProperty getStyleableProperty(JFXTextArea control) { - return control.disableAnimationProperty(); - } - }; - - private static final List> CHILD_STYLEABLES; - static { - final List> styleables = new ArrayList>(Control.getClassCssMetaData()); - Collections.addAll(styleables, UNFOCUS_COLOR, FOCUS_COLOR, LABEL_FLOAT, DISABLE_ANIMATION); - CHILD_STYLEABLES = Collections.unmodifiableList(styleables); - } - } - - // inherit the styleable properties from parent - private List> STYLEABLES; - - @Override - public List> getControlCssMetaData() { - if (STYLEABLES == null) { - final List> styleables = new ArrayList>(Control.getClassCssMetaData()); - styleables.addAll(getClassCssMetaData()); - styleables.addAll(super.getClassCssMetaData()); - STYLEABLES = Collections.unmodifiableList(styleables); - } - return STYLEABLES; - } - - public static List> getClassCssMetaData() { - return StyleableProperties.CHILD_STYLEABLES; - } -} +/* + * 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 com.jfoenix.skins.JFXTextAreaSkin; +import com.jfoenix.validation.base.ValidatorBase; +import com.sun.javafx.css.converters.BooleanConverter; +import com.sun.javafx.css.converters.PaintConverter; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.css.*; +import javafx.scene.control.Control; +import javafx.scene.control.Skin; +import javafx.scene.control.TextArea; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * JFXTextArea is the material design implementation of a text area. + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXTextArea extends TextArea{ + + /** + * {@inheritDoc} + */ + public JFXTextArea() { + super(); + initialize(); + } + + /** + * {@inheritDoc} + */ + public JFXTextArea(String text) { + super(text); + initialize(); + } + + /** + * {@inheritDoc} + */ + @Override + protected Skin createDefaultSkin() { + return new JFXTextAreaSkin(this); + } + + private void initialize() { + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + if(System.getProperty("java.vm.name").toLowerCase().equals("dalvik")){ + this.setStyle("-fx-skin: \"com.jfoenix.android.skins.JFXTextAreaSkinAndroid\";"); + } + } + + /** + * Initialize the style class to 'jfx-text-field'. + * + * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-text-area"; + + /*************************************************************************** + * * + * Properties * + * * + **************************************************************************/ + + /** + * holds the current active validator on the text area in case of validation error + */ + private ReadOnlyObjectWrapper activeValidator = new ReadOnlyObjectWrapper(); + + public ValidatorBase getActiveValidator() { + return activeValidator == null ? null : activeValidator.get(); + } + + public ReadOnlyObjectProperty activeValidatorProperty() { + return this.activeValidator.getReadOnlyProperty(); + } + + /** + * list of validators that will validate the text value upon calling + * {{@link #validate()} + */ + private ObservableList validators = FXCollections.observableArrayList(); + + public ObservableList getValidators() { + return validators; + } + + public void setValidators(ValidatorBase... validators) { + this.validators.addAll(validators); + } + + /** + * validates the text value using the list of validators provided by the user + * {{@link #setValidators(ValidatorBase...)} + * @return true if the value is valid else false + */ + public boolean validate() { + for (ValidatorBase validator : validators) { + if (validator.getSrcControl() == null) + validator.setSrcControl(this); + validator.validate(); + if (validator.getHasErrors()) { + activeValidator.set(validator); + return false; + } + } + activeValidator.set(null); + return true; + } + + public void resetValidation() { + getStyleClass().remove(activeValidator.get() == null? "" : activeValidator.get().getErrorStyleClass()); + pseudoClassStateChanged(ValidatorBase.PSEUDO_CLASS_ERROR, false); + activeValidator.set(null); + } + + /*************************************************************************** + * * + * styleable Properties * + * * + **************************************************************************/ + + /** + * set true to show a float the prompt text when focusing the field + */ + private StyleableBooleanProperty labelFloat = new SimpleStyleableBooleanProperty(StyleableProperties.LABEL_FLOAT, JFXTextArea.this, "lableFloat", false); + + public final StyleableBooleanProperty labelFloatProperty() { + return this.labelFloat; + } + + public final boolean isLabelFloat() { + return this.labelFloatProperty().get(); + } + + public final void setLabelFloat(final boolean labelFloat) { + this.labelFloatProperty().set(labelFloat); + } + + /** + * default color used when the text area is unfocused + */ + private StyleableObjectProperty unFocusColor = new SimpleStyleableObjectProperty(StyleableProperties.UNFOCUS_COLOR, JFXTextArea.this, "unFocusColor", Color.rgb(77, 77, 77)); + + public Paint getUnFocusColor() { + return unFocusColor == null ? Color.rgb(77, 77, 77) : unFocusColor.get(); + } + + public StyleableObjectProperty unFocusColorProperty() { + return this.unFocusColor; + } + + public void setUnFocusColor(Paint color) { + this.unFocusColor.set(color); + } + + /** + * default color used when the text area is focused + */ + private StyleableObjectProperty focusColor = new SimpleStyleableObjectProperty(StyleableProperties.FOCUS_COLOR, JFXTextArea.this, "focusColor", Color.valueOf("#4059A9")); + + public Paint getFocusColor() { + return focusColor == null ? Color.valueOf("#4059A9") : focusColor.get(); + } + + public StyleableObjectProperty focusColorProperty() { + return this.focusColor; + } + + public void setFocusColor(Paint color) { + this.focusColor.set(color); + } + + /** + * disable animation on validation + */ + private StyleableBooleanProperty disableAnimation = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_ANIMATION, JFXTextArea.this, "disableAnimation", false); + public final StyleableBooleanProperty disableAnimationProperty() { + return this.disableAnimation; + } + public final Boolean isDisableAnimation() { + return disableAnimation == null ? false : this.disableAnimationProperty().get(); + } + public final void setDisableAnimation(final Boolean disabled) { + this.disableAnimationProperty().set(disabled); + } + + private static class StyleableProperties { + private static final CssMetaData UNFOCUS_COLOR = new CssMetaData("-jfx-unfocus-color", PaintConverter.getInstance(), Color.rgb(77, 77, 77)) { + @Override + public boolean isSettable(JFXTextArea control) { + return control.unFocusColor == null || !control.unFocusColor.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXTextArea control) { + return control.unFocusColorProperty(); + } + }; + private static final CssMetaData FOCUS_COLOR = new CssMetaData("-jfx-focus-color", PaintConverter.getInstance(), Color.valueOf("#4059A9")) { + @Override + public boolean isSettable(JFXTextArea control) { + return control.focusColor == null || !control.focusColor.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXTextArea control) { + return control.focusColorProperty(); + } + }; + private static final CssMetaData LABEL_FLOAT = new CssMetaData("-jfx-label-float", BooleanConverter.getInstance(), false) { + @Override + public boolean isSettable(JFXTextArea control) { + return control.labelFloat == null || !control.labelFloat.isBound(); + } + + @Override + public StyleableBooleanProperty getStyleableProperty(JFXTextArea control) { + return control.labelFloatProperty(); + } + }; + + private static final CssMetaData< JFXTextArea, Boolean> DISABLE_ANIMATION = + new CssMetaData< JFXTextArea, Boolean>("-jfx-disable-animation", + BooleanConverter.getInstance(), false) { + @Override + public boolean isSettable(JFXTextArea control) { + return control.disableAnimation == null || !control.disableAnimation.isBound(); + } + @Override + public StyleableBooleanProperty getStyleableProperty(JFXTextArea control) { + return control.disableAnimationProperty(); + } + }; + + private static final List> CHILD_STYLEABLES; + static { + final List> styleables = new ArrayList>(Control.getClassCssMetaData()); + Collections.addAll(styleables, UNFOCUS_COLOR, FOCUS_COLOR, LABEL_FLOAT, DISABLE_ANIMATION); + CHILD_STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + // inherit the styleable properties from parent + private List> STYLEABLES; + + @Override + public List> getControlCssMetaData() { + if (STYLEABLES == null) { + final List> styleables = new ArrayList>(Control.getClassCssMetaData()); + styleables.addAll(getClassCssMetaData()); + styleables.addAll(super.getClassCssMetaData()); + STYLEABLES = Collections.unmodifiableList(styleables); + } + return STYLEABLES; + } + + public static List> getClassCssMetaData() { + return StyleableProperties.CHILD_STYLEABLES; + } +} diff --git a/src/com/jfoenix/controls/JFXTextField.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXTextField.java similarity index 97% rename from src/com/jfoenix/controls/JFXTextField.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXTextField.java index 7d941af4..e8edbe15 100644 --- a/src/com/jfoenix/controls/JFXTextField.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/JFXTextField.java @@ -1,292 +1,292 @@ -/* - * 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 com.jfoenix.skins.JFXTextFieldSkin; -import com.jfoenix.validation.base.ValidatorBase; -import com.sun.javafx.css.converters.BooleanConverter; -import com.sun.javafx.css.converters.PaintConverter; -import javafx.beans.property.ReadOnlyObjectProperty; -import javafx.beans.property.ReadOnlyObjectWrapper; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.css.*; -import javafx.scene.control.Control; -import javafx.scene.control.Skin; -import javafx.scene.control.TextField; -import javafx.scene.paint.Color; -import javafx.scene.paint.Paint; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * JFXTextField is the material design implementation of a text Field. - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXTextField extends TextField { - - /** - * {@inheritDoc} - */ - public JFXTextField() { - super(); - initialize(); - } - - /** - * {@inheritDoc} - */ - public JFXTextField(String text) { - super(text); - initialize(); - } - - /** - * {@inheritDoc} - */ - @Override - protected Skin createDefaultSkin() { - return new JFXTextFieldSkin(this); - } - - private void initialize() { - this.getStyleClass().add(DEFAULT_STYLE_CLASS); - if(System.getProperty("java.vm.name").toLowerCase().equals("dalvik")){ - this.setStyle("-fx-skin: \"com.jfoenix.android.skins.JFXTextFieldSkinAndroid\";"); - } - } - - /** - * Initialize the style class to 'jfx-text-field'. - * - * This is the selector class from which CSS can be used to style - * this control. - */ - private static final String DEFAULT_STYLE_CLASS = "jfx-text-field"; - - /*************************************************************************** - * * - * Properties * - * * - **************************************************************************/ - - /** - * holds the current active validator on the text field in case of validation error - */ - private ReadOnlyObjectWrapper activeValidator = new ReadOnlyObjectWrapper(); - - public ValidatorBase getActiveValidator() { - return activeValidator == null ? null : activeValidator.get(); - } - - public ReadOnlyObjectProperty activeValidatorProperty() { - return this.activeValidator.getReadOnlyProperty(); - } - - /** - * list of validators that will validate the text value upon calling - * {{@link #validate()} - */ - private ObservableList validators = FXCollections.observableArrayList(); - - public ObservableList getValidators() { - return validators; - } - - public void setValidators(ValidatorBase... validators) { - this.validators.addAll(validators); - } - - /** - * validates the text value using the list of validators provided by the user - * {{@link #setValidators(ValidatorBase...)} - * @return true if the value is valid else false - */ - public boolean validate() { - for (ValidatorBase validator : validators) { - if (validator.getSrcControl() == null) - validator.setSrcControl(this); - validator.validate(); - if (validator.getHasErrors()) { - activeValidator.set(validator); - return false; - } - } - activeValidator.set(null); - return true; - } - - public void resetValidation() { - getStyleClass().remove(activeValidator.get() == null? "" : activeValidator.get().getErrorStyleClass()); - pseudoClassStateChanged(ValidatorBase.PSEUDO_CLASS_ERROR, false); - activeValidator.set(null); - } - - /*************************************************************************** - * * - * styleable Properties * - * * - **************************************************************************/ - - /** - * set true to show a float the prompt text when focusing the field - */ - private StyleableBooleanProperty labelFloat = new SimpleStyleableBooleanProperty(StyleableProperties.LABEL_FLOAT, JFXTextField.this, "lableFloat", false); - - public final StyleableBooleanProperty labelFloatProperty() { - return this.labelFloat; - } - - public final boolean isLabelFloat() { - return this.labelFloatProperty().get(); - } - - public final void setLabelFloat(final boolean labelFloat) { - this.labelFloatProperty().set(labelFloat); - } - - /** - * default color used when the field is unfocused - */ - private StyleableObjectProperty unFocusColor = new SimpleStyleableObjectProperty(StyleableProperties.UNFOCUS_COLOR, JFXTextField.this, "unFocusColor", Color.rgb(77, 77, 77)); - - public Paint getUnFocusColor() { - return unFocusColor == null ? Color.rgb(77, 77, 77) : unFocusColor.get(); - } - - public StyleableObjectProperty unFocusColorProperty() { - return this.unFocusColor; - } - - public void setUnFocusColor(Paint color) { - this.unFocusColor.set(color); - } - - /** - * default color used when the field is focused - */ - private StyleableObjectProperty focusColor = new SimpleStyleableObjectProperty(StyleableProperties.FOCUS_COLOR, JFXTextField.this, "focusColor", Color.valueOf("#4059A9")); - - public Paint getFocusColor() { - return focusColor == null ? Color.valueOf("#4059A9") : focusColor.get(); - } - - public StyleableObjectProperty focusColorProperty() { - return this.focusColor; - } - - public void setFocusColor(Paint color) { - this.focusColor.set(color); - } - - /** - * disable animation on validation - */ - private StyleableBooleanProperty disableAnimation = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_ANIMATION, JFXTextField.this, "disableAnimation", false); - public final StyleableBooleanProperty disableAnimationProperty() { - return this.disableAnimation; - } - public final Boolean isDisableAnimation() { - return disableAnimation == null ? false : this.disableAnimationProperty().get(); - } - public final void setDisableAnimation(final Boolean disabled) { - this.disableAnimationProperty().set(disabled); - } - - - private static class StyleableProperties { - private static final CssMetaData UNFOCUS_COLOR = new CssMetaData("-jfx-unfocus-color", PaintConverter.getInstance(), Color.valueOf("#A6A6A6")) { - @Override - public boolean isSettable(JFXTextField control) { - return control.unFocusColor == null || !control.unFocusColor.isBound(); - } - - @Override - public StyleableProperty getStyleableProperty(JFXTextField control) { - return control.unFocusColorProperty(); - } - }; - private static final CssMetaData FOCUS_COLOR = new CssMetaData("-jfx-focus-color", PaintConverter.getInstance(), Color.valueOf("#3f51b5")) { - @Override - public boolean isSettable(JFXTextField control) { - return control.focusColor == null || !control.focusColor.isBound(); - } - - @Override - public StyleableProperty getStyleableProperty(JFXTextField control) { - return control.focusColorProperty(); - } - }; - private static final CssMetaData LABEL_FLOAT = new CssMetaData("-jfx-label-float", BooleanConverter.getInstance(), false) { - @Override - public boolean isSettable(JFXTextField control) { - return control.labelFloat == null || !control.labelFloat.isBound(); - } - - @Override - public StyleableBooleanProperty getStyleableProperty(JFXTextField control) { - return control.labelFloatProperty(); - } - }; - - private static final CssMetaData< JFXTextField, Boolean> DISABLE_ANIMATION = - new CssMetaData< JFXTextField, Boolean>("-jfx-disable-animation", - BooleanConverter.getInstance(), false) { - @Override - public boolean isSettable(JFXTextField control) { - return control.disableAnimation == null || !control.disableAnimation.isBound(); - } - @Override - public StyleableBooleanProperty getStyleableProperty(JFXTextField control) { - return control.disableAnimationProperty(); - } - }; - - - private static final List> CHILD_STYLEABLES; - static { - final List> styleables = new ArrayList>(Control.getClassCssMetaData()); - Collections.addAll(styleables, UNFOCUS_COLOR, FOCUS_COLOR, LABEL_FLOAT, DISABLE_ANIMATION); - CHILD_STYLEABLES = Collections.unmodifiableList(styleables); - } - } - - // inherit the styleable properties from parent - private List> STYLEABLES; - - @Override - public List> getControlCssMetaData() { - if (STYLEABLES == null) { - final List> styleables = new ArrayList>(Control.getClassCssMetaData()); - styleables.addAll(getClassCssMetaData()); - styleables.addAll(super.getClassCssMetaData()); - STYLEABLES = Collections.unmodifiableList(styleables); - } - return STYLEABLES; - } - - public static List> getClassCssMetaData() { - return StyleableProperties.CHILD_STYLEABLES; - } -} +/* + * 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 com.jfoenix.skins.JFXTextFieldSkin; +import com.jfoenix.validation.base.ValidatorBase; +import com.sun.javafx.css.converters.BooleanConverter; +import com.sun.javafx.css.converters.PaintConverter; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.css.*; +import javafx.scene.control.Control; +import javafx.scene.control.Skin; +import javafx.scene.control.TextField; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * JFXTextField is the material design implementation of a text Field. + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXTextField extends TextField { + + /** + * {@inheritDoc} + */ + public JFXTextField() { + super(); + initialize(); + } + + /** + * {@inheritDoc} + */ + public JFXTextField(String text) { + super(text); + initialize(); + } + + /** + * {@inheritDoc} + */ + @Override + protected Skin createDefaultSkin() { + return new JFXTextFieldSkin(this); + } + + private void initialize() { + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + if(System.getProperty("java.vm.name").toLowerCase().equals("dalvik")){ + this.setStyle("-fx-skin: \"com.jfoenix.android.skins.JFXTextFieldSkinAndroid\";"); + } + } + + /** + * Initialize the style class to 'jfx-text-field'. + * + * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-text-field"; + + /*************************************************************************** + * * + * Properties * + * * + **************************************************************************/ + + /** + * holds the current active validator on the text field in case of validation error + */ + private ReadOnlyObjectWrapper activeValidator = new ReadOnlyObjectWrapper(); + + public ValidatorBase getActiveValidator() { + return activeValidator == null ? null : activeValidator.get(); + } + + public ReadOnlyObjectProperty activeValidatorProperty() { + return this.activeValidator.getReadOnlyProperty(); + } + + /** + * list of validators that will validate the text value upon calling + * {{@link #validate()} + */ + private ObservableList validators = FXCollections.observableArrayList(); + + public ObservableList getValidators() { + return validators; + } + + public void setValidators(ValidatorBase... validators) { + this.validators.addAll(validators); + } + + /** + * validates the text value using the list of validators provided by the user + * {{@link #setValidators(ValidatorBase...)} + * @return true if the value is valid else false + */ + public boolean validate() { + for (ValidatorBase validator : validators) { + if (validator.getSrcControl() == null) + validator.setSrcControl(this); + validator.validate(); + if (validator.getHasErrors()) { + activeValidator.set(validator); + return false; + } + } + activeValidator.set(null); + return true; + } + + public void resetValidation() { + getStyleClass().remove(activeValidator.get() == null? "" : activeValidator.get().getErrorStyleClass()); + pseudoClassStateChanged(ValidatorBase.PSEUDO_CLASS_ERROR, false); + activeValidator.set(null); + } + + /*************************************************************************** + * * + * styleable Properties * + * * + **************************************************************************/ + + /** + * set true to show a float the prompt text when focusing the field + */ + private StyleableBooleanProperty labelFloat = new SimpleStyleableBooleanProperty(StyleableProperties.LABEL_FLOAT, JFXTextField.this, "lableFloat", false); + + public final StyleableBooleanProperty labelFloatProperty() { + return this.labelFloat; + } + + public final boolean isLabelFloat() { + return this.labelFloatProperty().get(); + } + + public final void setLabelFloat(final boolean labelFloat) { + this.labelFloatProperty().set(labelFloat); + } + + /** + * default color used when the field is unfocused + */ + private StyleableObjectProperty unFocusColor = new SimpleStyleableObjectProperty(StyleableProperties.UNFOCUS_COLOR, JFXTextField.this, "unFocusColor", Color.rgb(77, 77, 77)); + + public Paint getUnFocusColor() { + return unFocusColor == null ? Color.rgb(77, 77, 77) : unFocusColor.get(); + } + + public StyleableObjectProperty unFocusColorProperty() { + return this.unFocusColor; + } + + public void setUnFocusColor(Paint color) { + this.unFocusColor.set(color); + } + + /** + * default color used when the field is focused + */ + private StyleableObjectProperty focusColor = new SimpleStyleableObjectProperty(StyleableProperties.FOCUS_COLOR, JFXTextField.this, "focusColor", Color.valueOf("#4059A9")); + + public Paint getFocusColor() { + return focusColor == null ? Color.valueOf("#4059A9") : focusColor.get(); + } + + public StyleableObjectProperty focusColorProperty() { + return this.focusColor; + } + + public void setFocusColor(Paint color) { + this.focusColor.set(color); + } + + /** + * disable animation on validation + */ + private StyleableBooleanProperty disableAnimation = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_ANIMATION, JFXTextField.this, "disableAnimation", false); + public final StyleableBooleanProperty disableAnimationProperty() { + return this.disableAnimation; + } + public final Boolean isDisableAnimation() { + return disableAnimation == null ? false : this.disableAnimationProperty().get(); + } + public final void setDisableAnimation(final Boolean disabled) { + this.disableAnimationProperty().set(disabled); + } + + + private static class StyleableProperties { + private static final CssMetaData UNFOCUS_COLOR = new CssMetaData("-jfx-unfocus-color", PaintConverter.getInstance(), Color.valueOf("#A6A6A6")) { + @Override + public boolean isSettable(JFXTextField control) { + return control.unFocusColor == null || !control.unFocusColor.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXTextField control) { + return control.unFocusColorProperty(); + } + }; + private static final CssMetaData FOCUS_COLOR = new CssMetaData("-jfx-focus-color", PaintConverter.getInstance(), Color.valueOf("#3f51b5")) { + @Override + public boolean isSettable(JFXTextField control) { + return control.focusColor == null || !control.focusColor.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXTextField control) { + return control.focusColorProperty(); + } + }; + private static final CssMetaData LABEL_FLOAT = new CssMetaData("-jfx-label-float", BooleanConverter.getInstance(), false) { + @Override + public boolean isSettable(JFXTextField control) { + return control.labelFloat == null || !control.labelFloat.isBound(); + } + + @Override + public StyleableBooleanProperty getStyleableProperty(JFXTextField control) { + return control.labelFloatProperty(); + } + }; + + private static final CssMetaData< JFXTextField, Boolean> DISABLE_ANIMATION = + new CssMetaData< JFXTextField, Boolean>("-jfx-disable-animation", + BooleanConverter.getInstance(), false) { + @Override + public boolean isSettable(JFXTextField control) { + return control.disableAnimation == null || !control.disableAnimation.isBound(); + } + @Override + public StyleableBooleanProperty getStyleableProperty(JFXTextField control) { + return control.disableAnimationProperty(); + } + }; + + + private static final List> CHILD_STYLEABLES; + static { + final List> styleables = new ArrayList>(Control.getClassCssMetaData()); + Collections.addAll(styleables, UNFOCUS_COLOR, FOCUS_COLOR, LABEL_FLOAT, DISABLE_ANIMATION); + CHILD_STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + // inherit the styleable properties from parent + private List> STYLEABLES; + + @Override + public List> getControlCssMetaData() { + if (STYLEABLES == null) { + final List> styleables = new ArrayList>(Control.getClassCssMetaData()); + styleables.addAll(getClassCssMetaData()); + styleables.addAll(super.getClassCssMetaData()); + STYLEABLES = Collections.unmodifiableList(styleables); + } + return STYLEABLES; + } + + public static List> getClassCssMetaData() { + return StyleableProperties.CHILD_STYLEABLES; + } +} diff --git a/src/com/jfoenix/controls/JFXTimePicker.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXTimePicker.java similarity index 100% rename from src/com/jfoenix/controls/JFXTimePicker.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXTimePicker.java diff --git a/src/com/jfoenix/controls/JFXToggleButton.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXToggleButton.java similarity index 97% rename from src/com/jfoenix/controls/JFXToggleButton.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXToggleButton.java index 136a21e9..b9ccf826 100644 --- a/src/com/jfoenix/controls/JFXToggleButton.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/JFXToggleButton.java @@ -1,257 +1,257 @@ -/* - * 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 com.jfoenix.skins.JFXToggleButtonSkin; -import com.sun.javafx.css.converters.PaintConverter; -import javafx.css.*; -import javafx.scene.control.Control; -import javafx.scene.control.Skin; -import javafx.scene.control.ToggleButton; -import javafx.scene.paint.Color; -import javafx.scene.paint.Paint; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * JFXToggleButton is the material design implementation of a toggle button. - * important CSS Selectors: - * - * .jfx-toggle-button{ - * -fx-toggle-color: color-value; - * -fx-untoggle-color: color-value; - * -fx-toggle-line-color: color-value; - * -fx-untoggle-line-color: color-value; - * } - * - * To change the rippler color when toggled: - * - * .jfx-toggle-button .jfx-rippler{ - * -fx-rippler-fill: color-value; - * } - * - * .jfx-toggle-button:selected .jfx-rippler{ - * -fx-rippler-fill: color-value; - * } - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXToggleButton extends ToggleButton { - - /** - * {@inheritDoc} - */ - public JFXToggleButton() { - super(); - initialize(); - // init in scene builder workaround ( TODO : remove when JFoenix is well integrated in scenebuilder by gluon ) - StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); - for(int i = 0 ; i < stackTraceElements.length && i < 15; i++){ - if(stackTraceElements[i].getClassName().toLowerCase().contains(".scenebuilder.kit.fxom.")){ - this.setText("ToggleButton"); - break; - } - } - } - - /** - * {@inheritDoc} - */ - @Override - protected Skin createDefaultSkin() { - return new JFXToggleButtonSkin(this); - } - - private void initialize() { - this.getStyleClass().add(DEFAULT_STYLE_CLASS); - toggleColor.addListener((o,oldVal,newVal)->{ - // update line color in case not set by the user - toggleLineColor.set(((Color)getToggleColor()).desaturate().desaturate().brighter()); - }); - } - - /*************************************************************************** - * * - * styleable Properties * - * * - **************************************************************************/ - - /** - * Initialize the style class to 'jfx-toggle-button'. - * - * This is the selector class from which CSS can be used to style - * this control. - */ - private static final String DEFAULT_STYLE_CLASS = "jfx-toggle-button"; - - /** - * default color used when the button is toggled - */ - private StyleableObjectProperty toggleColor = new SimpleStyleableObjectProperty(StyleableProperties.TOGGLE_COLOR, JFXToggleButton.this, "toggleColor", Color.valueOf("#009688")); - - public Paint getToggleColor(){ - return toggleColor == null ? Color.valueOf("#009688") : toggleColor.get(); - } - public StyleableObjectProperty toggleColorProperty(){ - return this.toggleColor; - } - public void setToggleColor(Paint color){ - this.toggleColor.set(color); - } - - /** - * default color used when the button is not toggled - */ - private StyleableObjectProperty untoggleColor = new SimpleStyleableObjectProperty(StyleableProperties.UNTOGGLE_COLOR, JFXToggleButton.this, "unToggleColor", Color.valueOf("#FAFAFA")); - - public Paint getUnToggleColor(){ - return untoggleColor == null ? Color.valueOf("#FAFAFA") : untoggleColor.get(); - } - public StyleableObjectProperty unToggleColorProperty(){ - return this.untoggleColor; - } - public void setUnToggleColor(Paint color){ - this.untoggleColor.set(color); - } - - /** - * default line color used when the button is toggled - */ - private StyleableObjectProperty toggleLineColor = new SimpleStyleableObjectProperty(StyleableProperties.TOGGLE_LINE_COLOR, JFXToggleButton.this, "toggleLineColor", Color.valueOf("#77C2BB")); - - public Paint getToggleLineColor(){ - return toggleLineColor == null ? Color.valueOf("#77C2BB") : toggleLineColor.get(); - } - public StyleableObjectProperty toggleLineColorProperty(){ - return this.toggleLineColor; - } - public void setToggleLineColor(Paint color){ - this.toggleLineColor.set(color); - } - - /** - * default line color used when the button is not toggled - */ - private StyleableObjectProperty untoggleLineColor = new SimpleStyleableObjectProperty(StyleableProperties.UNTOGGLE_LINE_COLOR, JFXToggleButton.this, "unToggleLineColor", Color.valueOf("#999999")); - - public Paint getUnToggleLineColor(){ - return untoggleLineColor == null ? Color.valueOf("#999999") : untoggleLineColor.get(); - } - public StyleableObjectProperty unToggleLineColorProperty(){ - return this.untoggleLineColor; - } - public void setUnToggleLineColor(Paint color){ - this.untoggleLineColor.set(color); - } - - - private static class StyleableProperties { - private static final CssMetaData< JFXToggleButton, Paint> TOGGLE_COLOR = - new CssMetaData< JFXToggleButton, Paint>("-jfx-toggle-color", - PaintConverter.getInstance(), Color.valueOf("#009688")) { - @Override - public boolean isSettable(JFXToggleButton control) { - return control.toggleColor == null || !control.toggleColor.isBound(); - } - @Override - public StyleableProperty getStyleableProperty(JFXToggleButton control) { - return control.toggleColorProperty(); - } - }; - - private static final CssMetaData< JFXToggleButton, Paint> UNTOGGLE_COLOR = - new CssMetaData< JFXToggleButton, Paint>("-jfx-untoggle-color", - PaintConverter.getInstance(), Color.valueOf("#FAFAFA")) { - @Override - public boolean isSettable(JFXToggleButton control) { - return control.untoggleColor == null || !control.untoggleColor.isBound(); - } - @Override - public StyleableProperty getStyleableProperty(JFXToggleButton control) { - return control.unToggleColorProperty(); - } - }; - - private static final CssMetaData< JFXToggleButton, Paint> TOGGLE_LINE_COLOR = - new CssMetaData< JFXToggleButton, Paint>("-jfx-toggle-line-color", - PaintConverter.getInstance(), Color.valueOf("#77C2BB")) { - @Override - public boolean isSettable(JFXToggleButton control) { - return control.toggleLineColor == null || !control.toggleLineColor.isBound(); - } - @Override - public StyleableProperty getStyleableProperty(JFXToggleButton control) { - return control.toggleLineColorProperty(); - } - }; - - private static final CssMetaData< JFXToggleButton, Paint> UNTOGGLE_LINE_COLOR = - new CssMetaData< JFXToggleButton, Paint>("-jfx-untoggle-line-color", - PaintConverter.getInstance(), Color.valueOf("#999999")) { - @Override - public boolean isSettable(JFXToggleButton control) { - return control.untoggleLineColor == null || !control.untoggleLineColor.isBound(); - } - @Override - public StyleableProperty getStyleableProperty(JFXToggleButton control) { - return control.unToggleLineColorProperty(); - } - }; - - - - private static final List> CHILD_STYLEABLES; - static { - final List> styleables = - new ArrayList>(Control.getClassCssMetaData()); - Collections.addAll(styleables, - TOGGLE_COLOR, - UNTOGGLE_COLOR, - TOGGLE_LINE_COLOR, - UNTOGGLE_LINE_COLOR - ); - CHILD_STYLEABLES = Collections.unmodifiableList(styleables); - } - } - - // inherit the styleable properties from parent - private List> STYLEABLES; - - @Override - public List> getControlCssMetaData() { - if(STYLEABLES == null){ - final List> styleables = - new ArrayList>(Control.getClassCssMetaData()); - styleables.addAll(getClassCssMetaData()); - styleables.addAll(super.getClassCssMetaData()); - STYLEABLES = Collections.unmodifiableList(styleables); - } - return STYLEABLES; - } - public static List> getClassCssMetaData() { - return StyleableProperties.CHILD_STYLEABLES; - } - - - -} +/* + * 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 com.jfoenix.skins.JFXToggleButtonSkin; +import com.sun.javafx.css.converters.PaintConverter; +import javafx.css.*; +import javafx.scene.control.Control; +import javafx.scene.control.Skin; +import javafx.scene.control.ToggleButton; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * JFXToggleButton is the material design implementation of a toggle button. + * important CSS Selectors: + * + * .jfx-toggle-button{ + * -fx-toggle-color: color-value; + * -fx-untoggle-color: color-value; + * -fx-toggle-line-color: color-value; + * -fx-untoggle-line-color: color-value; + * } + * + * To change the rippler color when toggled: + * + * .jfx-toggle-button .jfx-rippler{ + * -fx-rippler-fill: color-value; + * } + * + * .jfx-toggle-button:selected .jfx-rippler{ + * -fx-rippler-fill: color-value; + * } + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXToggleButton extends ToggleButton { + + /** + * {@inheritDoc} + */ + public JFXToggleButton() { + super(); + initialize(); + // init in scene builder workaround ( TODO : remove when JFoenix is well integrated in scenebuilder by gluon ) + StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); + for(int i = 0 ; i < stackTraceElements.length && i < 15; i++){ + if(stackTraceElements[i].getClassName().toLowerCase().contains(".scenebuilder.kit.fxom.")){ + this.setText("ToggleButton"); + break; + } + } + } + + /** + * {@inheritDoc} + */ + @Override + protected Skin createDefaultSkin() { + return new JFXToggleButtonSkin(this); + } + + private void initialize() { + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + toggleColor.addListener((o,oldVal,newVal)->{ + // update line color in case not set by the user + toggleLineColor.set(((Color)getToggleColor()).desaturate().desaturate().brighter()); + }); + } + + /*************************************************************************** + * * + * styleable Properties * + * * + **************************************************************************/ + + /** + * Initialize the style class to 'jfx-toggle-button'. + * + * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-toggle-button"; + + /** + * default color used when the button is toggled + */ + private StyleableObjectProperty toggleColor = new SimpleStyleableObjectProperty(StyleableProperties.TOGGLE_COLOR, JFXToggleButton.this, "toggleColor", Color.valueOf("#009688")); + + public Paint getToggleColor(){ + return toggleColor == null ? Color.valueOf("#009688") : toggleColor.get(); + } + public StyleableObjectProperty toggleColorProperty(){ + return this.toggleColor; + } + public void setToggleColor(Paint color){ + this.toggleColor.set(color); + } + + /** + * default color used when the button is not toggled + */ + private StyleableObjectProperty untoggleColor = new SimpleStyleableObjectProperty(StyleableProperties.UNTOGGLE_COLOR, JFXToggleButton.this, "unToggleColor", Color.valueOf("#FAFAFA")); + + public Paint getUnToggleColor(){ + return untoggleColor == null ? Color.valueOf("#FAFAFA") : untoggleColor.get(); + } + public StyleableObjectProperty unToggleColorProperty(){ + return this.untoggleColor; + } + public void setUnToggleColor(Paint color){ + this.untoggleColor.set(color); + } + + /** + * default line color used when the button is toggled + */ + private StyleableObjectProperty toggleLineColor = new SimpleStyleableObjectProperty(StyleableProperties.TOGGLE_LINE_COLOR, JFXToggleButton.this, "toggleLineColor", Color.valueOf("#77C2BB")); + + public Paint getToggleLineColor(){ + return toggleLineColor == null ? Color.valueOf("#77C2BB") : toggleLineColor.get(); + } + public StyleableObjectProperty toggleLineColorProperty(){ + return this.toggleLineColor; + } + public void setToggleLineColor(Paint color){ + this.toggleLineColor.set(color); + } + + /** + * default line color used when the button is not toggled + */ + private StyleableObjectProperty untoggleLineColor = new SimpleStyleableObjectProperty(StyleableProperties.UNTOGGLE_LINE_COLOR, JFXToggleButton.this, "unToggleLineColor", Color.valueOf("#999999")); + + public Paint getUnToggleLineColor(){ + return untoggleLineColor == null ? Color.valueOf("#999999") : untoggleLineColor.get(); + } + public StyleableObjectProperty unToggleLineColorProperty(){ + return this.untoggleLineColor; + } + public void setUnToggleLineColor(Paint color){ + this.untoggleLineColor.set(color); + } + + + private static class StyleableProperties { + private static final CssMetaData< JFXToggleButton, Paint> TOGGLE_COLOR = + new CssMetaData< JFXToggleButton, Paint>("-jfx-toggle-color", + PaintConverter.getInstance(), Color.valueOf("#009688")) { + @Override + public boolean isSettable(JFXToggleButton control) { + return control.toggleColor == null || !control.toggleColor.isBound(); + } + @Override + public StyleableProperty getStyleableProperty(JFXToggleButton control) { + return control.toggleColorProperty(); + } + }; + + private static final CssMetaData< JFXToggleButton, Paint> UNTOGGLE_COLOR = + new CssMetaData< JFXToggleButton, Paint>("-jfx-untoggle-color", + PaintConverter.getInstance(), Color.valueOf("#FAFAFA")) { + @Override + public boolean isSettable(JFXToggleButton control) { + return control.untoggleColor == null || !control.untoggleColor.isBound(); + } + @Override + public StyleableProperty getStyleableProperty(JFXToggleButton control) { + return control.unToggleColorProperty(); + } + }; + + private static final CssMetaData< JFXToggleButton, Paint> TOGGLE_LINE_COLOR = + new CssMetaData< JFXToggleButton, Paint>("-jfx-toggle-line-color", + PaintConverter.getInstance(), Color.valueOf("#77C2BB")) { + @Override + public boolean isSettable(JFXToggleButton control) { + return control.toggleLineColor == null || !control.toggleLineColor.isBound(); + } + @Override + public StyleableProperty getStyleableProperty(JFXToggleButton control) { + return control.toggleLineColorProperty(); + } + }; + + private static final CssMetaData< JFXToggleButton, Paint> UNTOGGLE_LINE_COLOR = + new CssMetaData< JFXToggleButton, Paint>("-jfx-untoggle-line-color", + PaintConverter.getInstance(), Color.valueOf("#999999")) { + @Override + public boolean isSettable(JFXToggleButton control) { + return control.untoggleLineColor == null || !control.untoggleLineColor.isBound(); + } + @Override + public StyleableProperty getStyleableProperty(JFXToggleButton control) { + return control.unToggleLineColorProperty(); + } + }; + + + + private static final List> CHILD_STYLEABLES; + static { + final List> styleables = + new ArrayList>(Control.getClassCssMetaData()); + Collections.addAll(styleables, + TOGGLE_COLOR, + UNTOGGLE_COLOR, + TOGGLE_LINE_COLOR, + UNTOGGLE_LINE_COLOR + ); + CHILD_STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + // inherit the styleable properties from parent + private List> STYLEABLES; + + @Override + public List> getControlCssMetaData() { + if(STYLEABLES == null){ + final List> styleables = + new ArrayList>(Control.getClassCssMetaData()); + styleables.addAll(getClassCssMetaData()); + styleables.addAll(super.getClassCssMetaData()); + STYLEABLES = Collections.unmodifiableList(styleables); + } + return STYLEABLES; + } + public static List> getClassCssMetaData() { + return StyleableProperties.CHILD_STYLEABLES; + } + + + +} diff --git a/src/com/jfoenix/controls/JFXToggleNode.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXToggleNode.java similarity index 97% rename from src/com/jfoenix/controls/JFXToggleNode.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXToggleNode.java index f36d76e3..ae970899 100644 --- a/src/com/jfoenix/controls/JFXToggleNode.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/JFXToggleNode.java @@ -1,199 +1,199 @@ -/* - * 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 com.jfoenix.skins.JFXToggleNodeSkin; -import com.sun.javafx.css.converters.BooleanConverter; -import com.sun.javafx.css.converters.ColorConverter; -import javafx.beans.DefaultProperty; -import javafx.css.*; -import javafx.scene.control.Control; -import javafx.scene.control.Skin; -import javafx.scene.control.ToggleButton; -import javafx.scene.paint.Color; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * JFX Toggle Node , allows any node set as its graphic to be toggled - * not that JFXToggleNode background color MUST match the unselected - * color property, else the toggle animation will not be consistent. - * Notice that the default value for unselected color is set to - * transparent color. - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -@DefaultProperty(value="graphic") -public class JFXToggleNode extends ToggleButton { - - /** - * {@inheritDoc} - */ - public JFXToggleNode() { - super(); - initialize(); - } - - /** - * {@inheritDoc} - */ - @Override - protected Skin createDefaultSkin() { - return new JFXToggleNodeSkin(this); - } - - private void initialize() { - this.getStyleClass().add(DEFAULT_STYLE_CLASS); - } - - - /*************************************************************************** - * * - * Stylesheet Handling * - * * - **************************************************************************/ - - /** - * Initialize the style class to 'jfx-toggle-node'. - * - * This is the selector class from which CSS can be used to style - * this control. - */ - private static final String DEFAULT_STYLE_CLASS = "jfx-toggle-node"; - - /** - * default color used when the node is toggled - */ - private StyleableObjectProperty selectedColor = new SimpleStyleableObjectProperty(StyleableProperties.SELECTED_COLOR, JFXToggleNode.this, "selectedColor", Color.rgb(0, 0, 0, 0.2)); - - public final StyleableObjectProperty selectedColorProperty() { - return this.selectedColor; - } - public final Color getSelectedColor() { - return selectedColor == null ? Color.rgb(0, 0, 0, 0.2) : this.selectedColorProperty().get(); - } - public final void setSelectedColor(final Color selectedColor) { - this.selectedColorProperty().set(selectedColor); - } - - /** - * default color used when the node is not toggled - */ - private StyleableObjectProperty unSelectedColor = new SimpleStyleableObjectProperty(StyleableProperties.UNSELECTED_COLOR, JFXToggleNode.this, "unSelectedCOlor", Color.TRANSPARENT); - public final StyleableObjectProperty unSelectedColorProperty() { - return this.unSelectedColor; - } - public final Color getUnSelectedColor() { - return unSelectedColor == null ? Color.TRANSPARENT : this.unSelectedColorProperty().get(); - } - public final void setUnSelectedColor(final Color unSelectedColor) { - this.unSelectedColorProperty().set(unSelectedColor); - } - - /** - * disable animation on button action - */ - private StyleableBooleanProperty disableAnimation = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_ANIMATION, JFXToggleNode.this, "disableAnimation", false); - public final StyleableBooleanProperty disableAnimationProperty() { - return this.disableAnimation; - } - public final Boolean isDisableAnimation() { - return disableAnimation == null ? false : this.disableAnimationProperty().get(); - } - public final void setDisableAnimation(final Boolean disabled) { - this.disableAnimationProperty().set(disabled); - } - - - private static class StyleableProperties { - private static final CssMetaData< JFXToggleNode, Color> SELECTED_COLOR = - new CssMetaData< JFXToggleNode, Color>("-jfx-toggle-color", - ColorConverter.getInstance(), Color.rgb(255, 255, 255, 0.87)) { - @Override - public boolean isSettable(JFXToggleNode control) { - return control.selectedColor == null || !control.selectedColor.isBound(); - } - @Override - public StyleableProperty getStyleableProperty(JFXToggleNode control) { - return control.selectedColorProperty(); - } - }; - - private static final CssMetaData< JFXToggleNode, Color> UNSELECTED_COLOR = - new CssMetaData< JFXToggleNode, Color>("-jfx-untoggle-color", - ColorConverter.getInstance(), Color.TRANSPARENT) { - @Override - public boolean isSettable(JFXToggleNode control) { - return control.unSelectedColor == null || !control.unSelectedColor.isBound(); - } - @Override - public StyleableProperty getStyleableProperty(JFXToggleNode control) { - return control.unSelectedColorProperty(); - } - }; - - private static final CssMetaData< JFXToggleNode, Boolean> DISABLE_ANIMATION = - new CssMetaData< JFXToggleNode, Boolean>("-jfx-disable-animation", - BooleanConverter.getInstance(), false) { - @Override - public boolean isSettable(JFXToggleNode control) { - return control.disableAnimation == null || !control.disableAnimation.isBound(); - } - @Override - public StyleableBooleanProperty getStyleableProperty(JFXToggleNode control) { - return control.disableAnimationProperty(); - } - }; - - private static final List> CHILD_STYLEABLES; - static { - final List> styleables = - new ArrayList>(Control.getClassCssMetaData()); - Collections.addAll(styleables, - SELECTED_COLOR, - UNSELECTED_COLOR, - DISABLE_ANIMATION - ); - CHILD_STYLEABLES = Collections.unmodifiableList(styleables); - } - } - - // inherit the styleable properties from parent - private List> STYLEABLES; - - @Override - public List> getControlCssMetaData() { - if(STYLEABLES == null){ - final List> styleables = - new ArrayList>(Control.getClassCssMetaData()); - styleables.addAll(getClassCssMetaData()); - styleables.addAll(super.getClassCssMetaData()); - STYLEABLES = Collections.unmodifiableList(styleables); - } - return STYLEABLES; - } - public static List> getClassCssMetaData() { - return StyleableProperties.CHILD_STYLEABLES; - } - -} +/* + * 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 com.jfoenix.skins.JFXToggleNodeSkin; +import com.sun.javafx.css.converters.BooleanConverter; +import com.sun.javafx.css.converters.ColorConverter; +import javafx.beans.DefaultProperty; +import javafx.css.*; +import javafx.scene.control.Control; +import javafx.scene.control.Skin; +import javafx.scene.control.ToggleButton; +import javafx.scene.paint.Color; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * JFX Toggle Node , allows any node set as its graphic to be toggled + * not that JFXToggleNode background color MUST match the unselected + * color property, else the toggle animation will not be consistent. + * Notice that the default value for unselected color is set to + * transparent color. + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +@DefaultProperty(value="graphic") +public class JFXToggleNode extends ToggleButton { + + /** + * {@inheritDoc} + */ + public JFXToggleNode() { + super(); + initialize(); + } + + /** + * {@inheritDoc} + */ + @Override + protected Skin createDefaultSkin() { + return new JFXToggleNodeSkin(this); + } + + private void initialize() { + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + } + + + /*************************************************************************** + * * + * Stylesheet Handling * + * * + **************************************************************************/ + + /** + * Initialize the style class to 'jfx-toggle-node'. + * + * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-toggle-node"; + + /** + * default color used when the node is toggled + */ + private StyleableObjectProperty selectedColor = new SimpleStyleableObjectProperty(StyleableProperties.SELECTED_COLOR, JFXToggleNode.this, "selectedColor", Color.rgb(0, 0, 0, 0.2)); + + public final StyleableObjectProperty selectedColorProperty() { + return this.selectedColor; + } + public final Color getSelectedColor() { + return selectedColor == null ? Color.rgb(0, 0, 0, 0.2) : this.selectedColorProperty().get(); + } + public final void setSelectedColor(final Color selectedColor) { + this.selectedColorProperty().set(selectedColor); + } + + /** + * default color used when the node is not toggled + */ + private StyleableObjectProperty unSelectedColor = new SimpleStyleableObjectProperty(StyleableProperties.UNSELECTED_COLOR, JFXToggleNode.this, "unSelectedCOlor", Color.TRANSPARENT); + public final StyleableObjectProperty unSelectedColorProperty() { + return this.unSelectedColor; + } + public final Color getUnSelectedColor() { + return unSelectedColor == null ? Color.TRANSPARENT : this.unSelectedColorProperty().get(); + } + public final void setUnSelectedColor(final Color unSelectedColor) { + this.unSelectedColorProperty().set(unSelectedColor); + } + + /** + * disable animation on button action + */ + private StyleableBooleanProperty disableAnimation = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_ANIMATION, JFXToggleNode.this, "disableAnimation", false); + public final StyleableBooleanProperty disableAnimationProperty() { + return this.disableAnimation; + } + public final Boolean isDisableAnimation() { + return disableAnimation == null ? false : this.disableAnimationProperty().get(); + } + public final void setDisableAnimation(final Boolean disabled) { + this.disableAnimationProperty().set(disabled); + } + + + private static class StyleableProperties { + private static final CssMetaData< JFXToggleNode, Color> SELECTED_COLOR = + new CssMetaData< JFXToggleNode, Color>("-jfx-toggle-color", + ColorConverter.getInstance(), Color.rgb(255, 255, 255, 0.87)) { + @Override + public boolean isSettable(JFXToggleNode control) { + return control.selectedColor == null || !control.selectedColor.isBound(); + } + @Override + public StyleableProperty getStyleableProperty(JFXToggleNode control) { + return control.selectedColorProperty(); + } + }; + + private static final CssMetaData< JFXToggleNode, Color> UNSELECTED_COLOR = + new CssMetaData< JFXToggleNode, Color>("-jfx-untoggle-color", + ColorConverter.getInstance(), Color.TRANSPARENT) { + @Override + public boolean isSettable(JFXToggleNode control) { + return control.unSelectedColor == null || !control.unSelectedColor.isBound(); + } + @Override + public StyleableProperty getStyleableProperty(JFXToggleNode control) { + return control.unSelectedColorProperty(); + } + }; + + private static final CssMetaData< JFXToggleNode, Boolean> DISABLE_ANIMATION = + new CssMetaData< JFXToggleNode, Boolean>("-jfx-disable-animation", + BooleanConverter.getInstance(), false) { + @Override + public boolean isSettable(JFXToggleNode control) { + return control.disableAnimation == null || !control.disableAnimation.isBound(); + } + @Override + public StyleableBooleanProperty getStyleableProperty(JFXToggleNode control) { + return control.disableAnimationProperty(); + } + }; + + private static final List> CHILD_STYLEABLES; + static { + final List> styleables = + new ArrayList>(Control.getClassCssMetaData()); + Collections.addAll(styleables, + SELECTED_COLOR, + UNSELECTED_COLOR, + DISABLE_ANIMATION + ); + CHILD_STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + // inherit the styleable properties from parent + private List> STYLEABLES; + + @Override + public List> getControlCssMetaData() { + if(STYLEABLES == null){ + final List> styleables = + new ArrayList>(Control.getClassCssMetaData()); + styleables.addAll(getClassCssMetaData()); + styleables.addAll(super.getClassCssMetaData()); + STYLEABLES = Collections.unmodifiableList(styleables); + } + return STYLEABLES; + } + public static List> getClassCssMetaData() { + return StyleableProperties.CHILD_STYLEABLES; + } + +} diff --git a/src/com/jfoenix/controls/JFXTogglePane.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXTogglePane.java similarity index 97% rename from src/com/jfoenix/controls/JFXTogglePane.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXTogglePane.java index df3f39e2..11aeefb7 100644 --- a/src/com/jfoenix/controls/JFXTogglePane.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/JFXTogglePane.java @@ -1,178 +1,178 @@ -/* - * 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.Interpolator; -import javafx.animation.KeyFrame; -import javafx.animation.KeyValue; -import javafx.animation.Timeline; -import javafx.beans.binding.Bindings; -import javafx.beans.property.DoubleProperty; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleDoubleProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.scene.Node; -import javafx.scene.control.Control; -import javafx.scene.layout.Region; -import javafx.scene.layout.StackPane; -import javafx.util.Duration; - -/** - * @author Shadi Shaheen - * toggle pane is a hidden pane that is shown upon mouse click on its toggle node - * NOTE : the toggle animation is specified by the pane clipping shape - * TODO : reimplement, currently not used - */ -public class JFXTogglePane extends StackPane { - - private Timeline toggleAnimation = null; - - private ObjectProperty toggleNode = new SimpleObjectProperty<>(); - - private ObjectProperty contentNode = new SimpleObjectProperty<>(); - - /* - * the scaling factor controls area of the clipping node when - * toggeling the pane - * - */ - private DoubleProperty scalingFactor = new SimpleDoubleProperty(2.4); - - public JFXTogglePane() { - - this.clipProperty().addListener((o,oldVal,newVal)->{ - if(newVal != null){ - if(getToggleNode()!=null){ - Region toggleNode = getToggleNode(); - newVal.layoutXProperty().bind(Bindings.createDoubleBinding(()-> toggleNode.getLayoutX() + toggleNode.getWidth()/2, toggleNode.widthProperty(), toggleNode.layoutXProperty())); - newVal.layoutYProperty().bind(Bindings.createDoubleBinding(()-> toggleNode.getLayoutY() + toggleNode.getHeight()/2, toggleNode.heightProperty(), toggleNode.layoutYProperty())); - } - } - }); - - this.widthProperty().addListener((o,oldVal,newVal) -> updateToggleAnimation()); - this.heightProperty().addListener((o,oldVal,newVal) -> updateToggleAnimation()); - - toggleNode.addListener((o,oldVal,newVal)-> { - if(newVal != null){ - if(getClip()!=null){ - getClip().layoutXProperty().unbind(); - getClip().layoutYProperty().unbind(); - getClip().layoutXProperty().bind(Bindings.createDoubleBinding(()-> newVal.getLayoutX() + newVal.getWidth()/2, newVal.widthProperty(), newVal.layoutXProperty())); - getClip().layoutYProperty().bind(Bindings.createDoubleBinding(()-> newVal.getLayoutY() + newVal.getHeight()/2, newVal.heightProperty(), newVal.layoutYProperty())); - } - } - updateToggleAnimation(); - newVal.widthProperty().addListener((o1,oldVal1,newVal1)-> updateToggleAnimation()); - newVal.heightProperty().addListener((o1,oldVal1,newVal1)-> updateToggleAnimation()); - newVal.setOnMouseClicked((click)->togglePane()); - }); - - } - - public void togglePane(){ - if(toggleAnimation == null) updateToggleAnimation(); - this.getClip().scaleXProperty().unbind(); - this.getClip().scaleYProperty().unbind(); - toggleAnimation.setRate(toggleAnimation.getRate() * -1); - if(toggleAnimation.getCurrentTime().equals(Duration.millis(0)) && toggleAnimation.getRate() == -1) toggleAnimation.playFrom(Duration.millis(510)); - else toggleAnimation.play(); - } - - - private void updateToggleAnimation(){ - if(getContentNode() == null) return; - double rateX = this.getWidth()/getClip().getLayoutBounds().getWidth(); - double rateY = this.getHeight()/getClip().getLayoutBounds().getHeight(); - double newRate = Math.max(rateX, rateY) * getScalingFactor(); - double animationRate = toggleAnimation == null ? -1 : toggleAnimation.getRate(); - - toggleAnimation = new Timeline( - new KeyFrame(Duration.millis(0), new KeyValue(getClip().scaleXProperty(), 1 , Interpolator.EASE_BOTH)), - new KeyFrame(Duration.millis(0), new KeyValue(getClip().scaleYProperty(), 1 , Interpolator.EASE_BOTH)), - new KeyFrame(Duration.millis(0), new KeyValue(getContentNode().opacityProperty(), 0 , Interpolator.EASE_BOTH)), - new KeyFrame(Duration.millis(350), new KeyValue(getClip().scaleXProperty(), newRate , Interpolator.EASE_BOTH)), - new KeyFrame(Duration.millis(350), new KeyValue(getClip().scaleYProperty(), newRate , Interpolator.EASE_BOTH)), - new KeyFrame(Duration.millis(370), new KeyValue(getContentNode().opacityProperty(), 0 , Interpolator.EASE_BOTH)), - new KeyFrame(Duration.millis(510), new KeyValue(getContentNode().opacityProperty(), 1 , Interpolator.EASE_BOTH))); - toggleAnimation.setOnFinished((finish)->{ - if(toggleAnimation.getRate() == 1){ - this.getClip().scaleXProperty().bind(Bindings.createDoubleBinding(()->{ - double X = this.getWidth()/getClip().getLayoutBounds().getWidth(); - double Y = this.getHeight()/getClip().getLayoutBounds().getHeight(); - double scale = Math.max(X, Y) * getScalingFactor(); - return scale; - }, this.widthProperty(), this.heightProperty())); - - this.getClip().scaleYProperty().bind(Bindings.createDoubleBinding(()->{ - double X = this.getWidth()/getClip().getLayoutBounds().getWidth(); - double Y = this.getHeight()/getClip().getLayoutBounds().getHeight(); - double scale = Math.max(X, Y) * getScalingFactor(); - return scale; - }, this.widthProperty(), this.heightProperty())); - } - }); - toggleAnimation.setRate(animationRate); - } - - /*************************************************************************** - * * - * Public Properties * - * * - **************************************************************************/ - - public final ObjectProperty toggleNodeProperty() { - return this.toggleNode; - } - - public final Control getToggleNode() { - return this.toggleNodeProperty().get(); - } - - public final void setToggleNode(final Control toggleNode) { - this.toggleNodeProperty().set(toggleNode); - } - - public final ObjectProperty contentNodeProperty() { - return this.contentNode; - } - - public final Node getContentNode() { - return this.contentNodeProperty().get(); - } - - public final void setContentNode(final Node content) { - this.contentNodeProperty().set(content); - content.setOpacity(0); - } - - public final DoubleProperty scalingFactorProperty() { - return this.scalingFactor; - } - - public final double getScalingFactor() { - return this.scalingFactorProperty().get(); - } - - public final void setScalingFactor(final double scalingFactor) { - this.scalingFactorProperty().set(scalingFactor); - } - - -} +/* + * 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.Interpolator; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.beans.binding.Bindings; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.scene.Node; +import javafx.scene.control.Control; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; +import javafx.util.Duration; + +/** + * @author Shadi Shaheen + * toggle pane is a hidden pane that is shown upon mouse click on its toggle node + * NOTE : the toggle animation is specified by the pane clipping shape + * TODO : reimplement, currently not used + */ +public class JFXTogglePane extends StackPane { + + private Timeline toggleAnimation = null; + + private ObjectProperty toggleNode = new SimpleObjectProperty<>(); + + private ObjectProperty contentNode = new SimpleObjectProperty<>(); + + /* + * the scaling factor controls area of the clipping node when + * toggeling the pane + * + */ + private DoubleProperty scalingFactor = new SimpleDoubleProperty(2.4); + + public JFXTogglePane() { + + this.clipProperty().addListener((o,oldVal,newVal)->{ + if(newVal != null){ + if(getToggleNode()!=null){ + Region toggleNode = getToggleNode(); + newVal.layoutXProperty().bind(Bindings.createDoubleBinding(()-> toggleNode.getLayoutX() + toggleNode.getWidth()/2, toggleNode.widthProperty(), toggleNode.layoutXProperty())); + newVal.layoutYProperty().bind(Bindings.createDoubleBinding(()-> toggleNode.getLayoutY() + toggleNode.getHeight()/2, toggleNode.heightProperty(), toggleNode.layoutYProperty())); + } + } + }); + + this.widthProperty().addListener((o,oldVal,newVal) -> updateToggleAnimation()); + this.heightProperty().addListener((o,oldVal,newVal) -> updateToggleAnimation()); + + toggleNode.addListener((o,oldVal,newVal)-> { + if(newVal != null){ + if(getClip()!=null){ + getClip().layoutXProperty().unbind(); + getClip().layoutYProperty().unbind(); + getClip().layoutXProperty().bind(Bindings.createDoubleBinding(()-> newVal.getLayoutX() + newVal.getWidth()/2, newVal.widthProperty(), newVal.layoutXProperty())); + getClip().layoutYProperty().bind(Bindings.createDoubleBinding(()-> newVal.getLayoutY() + newVal.getHeight()/2, newVal.heightProperty(), newVal.layoutYProperty())); + } + } + updateToggleAnimation(); + newVal.widthProperty().addListener((o1,oldVal1,newVal1)-> updateToggleAnimation()); + newVal.heightProperty().addListener((o1,oldVal1,newVal1)-> updateToggleAnimation()); + newVal.setOnMouseClicked((click)->togglePane()); + }); + + } + + public void togglePane(){ + if(toggleAnimation == null) updateToggleAnimation(); + this.getClip().scaleXProperty().unbind(); + this.getClip().scaleYProperty().unbind(); + toggleAnimation.setRate(toggleAnimation.getRate() * -1); + if(toggleAnimation.getCurrentTime().equals(Duration.millis(0)) && toggleAnimation.getRate() == -1) toggleAnimation.playFrom(Duration.millis(510)); + else toggleAnimation.play(); + } + + + private void updateToggleAnimation(){ + if(getContentNode() == null) return; + double rateX = this.getWidth()/getClip().getLayoutBounds().getWidth(); + double rateY = this.getHeight()/getClip().getLayoutBounds().getHeight(); + double newRate = Math.max(rateX, rateY) * getScalingFactor(); + double animationRate = toggleAnimation == null ? -1 : toggleAnimation.getRate(); + + toggleAnimation = new Timeline( + new KeyFrame(Duration.millis(0), new KeyValue(getClip().scaleXProperty(), 1 , Interpolator.EASE_BOTH)), + new KeyFrame(Duration.millis(0), new KeyValue(getClip().scaleYProperty(), 1 , Interpolator.EASE_BOTH)), + new KeyFrame(Duration.millis(0), new KeyValue(getContentNode().opacityProperty(), 0 , Interpolator.EASE_BOTH)), + new KeyFrame(Duration.millis(350), new KeyValue(getClip().scaleXProperty(), newRate , Interpolator.EASE_BOTH)), + new KeyFrame(Duration.millis(350), new KeyValue(getClip().scaleYProperty(), newRate , Interpolator.EASE_BOTH)), + new KeyFrame(Duration.millis(370), new KeyValue(getContentNode().opacityProperty(), 0 , Interpolator.EASE_BOTH)), + new KeyFrame(Duration.millis(510), new KeyValue(getContentNode().opacityProperty(), 1 , Interpolator.EASE_BOTH))); + toggleAnimation.setOnFinished((finish)->{ + if(toggleAnimation.getRate() == 1){ + this.getClip().scaleXProperty().bind(Bindings.createDoubleBinding(()->{ + double X = this.getWidth()/getClip().getLayoutBounds().getWidth(); + double Y = this.getHeight()/getClip().getLayoutBounds().getHeight(); + double scale = Math.max(X, Y) * getScalingFactor(); + return scale; + }, this.widthProperty(), this.heightProperty())); + + this.getClip().scaleYProperty().bind(Bindings.createDoubleBinding(()->{ + double X = this.getWidth()/getClip().getLayoutBounds().getWidth(); + double Y = this.getHeight()/getClip().getLayoutBounds().getHeight(); + double scale = Math.max(X, Y) * getScalingFactor(); + return scale; + }, this.widthProperty(), this.heightProperty())); + } + }); + toggleAnimation.setRate(animationRate); + } + + /*************************************************************************** + * * + * Public Properties * + * * + **************************************************************************/ + + public final ObjectProperty toggleNodeProperty() { + return this.toggleNode; + } + + public final Control getToggleNode() { + return this.toggleNodeProperty().get(); + } + + public final void setToggleNode(final Control toggleNode) { + this.toggleNodeProperty().set(toggleNode); + } + + public final ObjectProperty contentNodeProperty() { + return this.contentNode; + } + + public final Node getContentNode() { + return this.contentNodeProperty().get(); + } + + public final void setContentNode(final Node content) { + this.contentNodeProperty().set(content); + content.setOpacity(0); + } + + public final DoubleProperty scalingFactorProperty() { + return this.scalingFactor; + } + + public final double getScalingFactor() { + return this.scalingFactorProperty().get(); + } + + public final void setScalingFactor(final double scalingFactor) { + this.scalingFactorProperty().set(scalingFactor); + } + + +} diff --git a/src/com/jfoenix/controls/JFXToolbar.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXToolbar.java similarity index 97% rename from src/com/jfoenix/controls/JFXToolbar.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXToolbar.java index 9e609e98..415544fe 100644 --- a/src/com/jfoenix/controls/JFXToolbar.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/JFXToolbar.java @@ -1,95 +1,95 @@ -/* - * 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 com.jfoenix.effects.JFXDepthManager; -import javafx.collections.ObservableList; -import javafx.scene.Node; -import javafx.scene.layout.BorderPane; -import javafx.scene.layout.HBox; - -/** - * JFXToolbar is the material design implementation of a tool bar. - * toolbar is a borderpane, where the right/left content are HBoxs - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXToolbar extends BorderPane { - - private HBox leftBox = new HBox(); - private HBox rightBox = new HBox(); - - /** - * creates empty tool bar - */ - public JFXToolbar() { - initialize(); - this.setLeft(leftBox); - leftBox.getStyleClass().add("tool-bar-left-box"); - leftBox.setPickOnBounds(false); - this.setRight(rightBox); - rightBox.getStyleClass().add("tool-bar-right-box"); - rightBox.setPickOnBounds(false); - JFXDepthManager.setDepth(this, 1); - } - - /*************************************************************************** - * * - * Setters / Getters * - * * - **************************************************************************/ - - public void setLeftItems(Node... nodes){ - this.leftBox.getChildren().setAll(nodes); - } - - public ObservableList getLeftItems(){ - return this.leftBox.getChildren(); - } - - public void setRightItems(Node... nodes){ - this.rightBox.getChildren().setAll(nodes); - } - - public ObservableList getRightItems(){ - return this.rightBox.getChildren(); - } - - /*************************************************************************** - * * - * Stylesheet Handling * - * * - **************************************************************************/ - - /** - * Initialize the style class to 'jfx-tool-bar'. - * - * This is the selector class from which CSS can be used to style - * this control. - */ - private static final String DEFAULT_STYLE_CLASS = "jfx-tool-bar"; - - private void initialize() { - this.getStyleClass().add(DEFAULT_STYLE_CLASS); - } - - -} +/* + * 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 com.jfoenix.effects.JFXDepthManager; +import javafx.collections.ObservableList; +import javafx.scene.Node; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; + +/** + * JFXToolbar is the material design implementation of a tool bar. + * toolbar is a borderpane, where the right/left content are HBoxs + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXToolbar extends BorderPane { + + private HBox leftBox = new HBox(); + private HBox rightBox = new HBox(); + + /** + * creates empty tool bar + */ + public JFXToolbar() { + initialize(); + this.setLeft(leftBox); + leftBox.getStyleClass().add("tool-bar-left-box"); + leftBox.setPickOnBounds(false); + this.setRight(rightBox); + rightBox.getStyleClass().add("tool-bar-right-box"); + rightBox.setPickOnBounds(false); + JFXDepthManager.setDepth(this, 1); + } + + /*************************************************************************** + * * + * Setters / Getters * + * * + **************************************************************************/ + + public void setLeftItems(Node... nodes){ + this.leftBox.getChildren().setAll(nodes); + } + + public ObservableList getLeftItems(){ + return this.leftBox.getChildren(); + } + + public void setRightItems(Node... nodes){ + this.rightBox.getChildren().setAll(nodes); + } + + public ObservableList getRightItems(){ + return this.rightBox.getChildren(); + } + + /*************************************************************************** + * * + * Stylesheet Handling * + * * + **************************************************************************/ + + /** + * Initialize the style class to 'jfx-tool-bar'. + * + * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-tool-bar"; + + private void initialize() { + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + } + + +} diff --git a/src/com/jfoenix/controls/JFXTreeCell.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXTreeCell.java similarity index 100% rename from src/com/jfoenix/controls/JFXTreeCell.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXTreeCell.java diff --git a/src/com/jfoenix/controls/JFXTreeTableColumn.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXTreeTableColumn.java similarity index 97% rename from src/com/jfoenix/controls/JFXTreeTableColumn.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXTreeTableColumn.java index 275565f0..cc62675c 100644 --- a/src/com/jfoenix/controls/JFXTreeTableColumn.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/JFXTreeTableColumn.java @@ -1,142 +1,142 @@ -/* - * 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 com.jfoenix.controls.cells.editors.base.JFXTreeTableCell; -import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject; -import javafx.application.Platform; -import javafx.beans.property.ReadOnlyObjectWrapper; -import javafx.beans.value.ObservableValue; -import javafx.scene.Node; -import javafx.scene.control.ContextMenu; -import javafx.scene.control.MenuItem; -import javafx.scene.control.TreeTableCell; -import javafx.scene.control.TreeTableColumn; -import javafx.util.Callback; - -/** - * JFXTreeTableColumn is used by {@Link JFXTreeTableView}, it supports grouping functionality - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXTreeTableColumn extends TreeTableColumn { - - /** - * {@inheritDoc} - */ - public JFXTreeTableColumn() { - super(); - init(); - } - /** - * {@inheritDoc} - */ - public JFXTreeTableColumn(String text){ - super(text); - init(); - } - - private void init(){ - this.setCellFactory(new Callback, TreeTableCell>() { - @Override - public TreeTableCell call(TreeTableColumn param) { - return new JFXTreeTableCell(){ - @Override protected void updateItem(T item, boolean empty) { - if (item == getItem()) return; - super.updateItem(item, empty); - if (item == null) { - super.setText(null); - super.setGraphic(null); - } else if (item instanceof Node) { - super.setText(null); - super.setGraphic((Node)item); - } else { - super.setText(item.toString()); - super.setGraphic(null); - } - } - }; - } - }); - - Platform.runLater(()->{ - final ContextMenu contextMenu = new ContextMenu(); - // contextMenu.setOnShowing((showing)->{ - // System.out.println("showing"); - // }); - // contextMenu.setOnShown((shown)->{ - // System.out.println("shown"); - // }); - MenuItem item1 = new MenuItem("Group"); - item1.setOnAction((action)->{ - ((JFXTreeTableView)getTreeTableView()).group(this); - }); - MenuItem item2 = new MenuItem("UnGroup"); - item2.setOnAction((action)->{ ((JFXTreeTableView)getTreeTableView()).unGroup(this); }); - contextMenu.getItems().addAll(item1, item2); - setContextMenu(contextMenu); - }); - } - - /** - * validates the value of the tree item, - * this method also hides the column value for the grouped nodes - * - * @param param tree item - * @return true if the value is valid else false - */ - public final boolean validateValue(CellDataFeatures param){ - Object rowObject = param.getValue().getValue(); - if((rowObject instanceof RecursiveTreeObject && rowObject.getClass() == RecursiveTreeObject.class) - || (param.getTreeTableView() instanceof JFXTreeTableView - && ((JFXTreeTableView)param.getTreeTableView()).getGroupOrder().contains(this) - // make sure the node is a direct child to a group node - && param.getValue().getParent() != null - && param.getValue().getParent().getValue().getClass() == RecursiveTreeObject.class - )) - return false; - return true; - } - - /** - * @param param tree item - * @return the data represented by the tree item - */ - public final ObservableValue getComputedValue(CellDataFeatures param){ - Object rowObject = param.getValue().getValue(); - if(rowObject instanceof RecursiveTreeObject){ - RecursiveTreeObject item = (RecursiveTreeObject) rowObject; - if(item.getGroupedColumn() == this) - return new ReadOnlyObjectWrapper(item.getGroupedValue()); - } - return null; - } - - /** - * @return true if the column is grouped else false - */ - public boolean isGrouped() { - if(getTreeTableView() instanceof JFXTreeTableView && ((JFXTreeTableView)getTreeTableView()).getGroupOrder().contains(this)) - return true; - return 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.controls; + +import com.jfoenix.controls.cells.editors.base.JFXTreeTableCell; +import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject; +import javafx.application.Platform; +import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.beans.value.ObservableValue; +import javafx.scene.Node; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.MenuItem; +import javafx.scene.control.TreeTableCell; +import javafx.scene.control.TreeTableColumn; +import javafx.util.Callback; + +/** + * JFXTreeTableColumn is used by {@Link JFXTreeTableView}, it supports grouping functionality + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXTreeTableColumn extends TreeTableColumn { + + /** + * {@inheritDoc} + */ + public JFXTreeTableColumn() { + super(); + init(); + } + /** + * {@inheritDoc} + */ + public JFXTreeTableColumn(String text){ + super(text); + init(); + } + + private void init(){ + this.setCellFactory(new Callback, TreeTableCell>() { + @Override + public TreeTableCell call(TreeTableColumn param) { + return new JFXTreeTableCell(){ + @Override protected void updateItem(T item, boolean empty) { + if (item == getItem()) return; + super.updateItem(item, empty); + if (item == null) { + super.setText(null); + super.setGraphic(null); + } else if (item instanceof Node) { + super.setText(null); + super.setGraphic((Node)item); + } else { + super.setText(item.toString()); + super.setGraphic(null); + } + } + }; + } + }); + + Platform.runLater(()->{ + final ContextMenu contextMenu = new ContextMenu(); + // contextMenu.setOnShowing((showing)->{ + // System.out.println("showing"); + // }); + // contextMenu.setOnShown((shown)->{ + // System.out.println("shown"); + // }); + MenuItem item1 = new MenuItem("Group"); + item1.setOnAction((action)->{ + ((JFXTreeTableView)getTreeTableView()).group(this); + }); + MenuItem item2 = new MenuItem("UnGroup"); + item2.setOnAction((action)->{ ((JFXTreeTableView)getTreeTableView()).unGroup(this); }); + contextMenu.getItems().addAll(item1, item2); + setContextMenu(contextMenu); + }); + } + + /** + * validates the value of the tree item, + * this method also hides the column value for the grouped nodes + * + * @param param tree item + * @return true if the value is valid else false + */ + public final boolean validateValue(CellDataFeatures param){ + Object rowObject = param.getValue().getValue(); + if((rowObject instanceof RecursiveTreeObject && rowObject.getClass() == RecursiveTreeObject.class) + || (param.getTreeTableView() instanceof JFXTreeTableView + && ((JFXTreeTableView)param.getTreeTableView()).getGroupOrder().contains(this) + // make sure the node is a direct child to a group node + && param.getValue().getParent() != null + && param.getValue().getParent().getValue().getClass() == RecursiveTreeObject.class + )) + return false; + return true; + } + + /** + * @param param tree item + * @return the data represented by the tree item + */ + public final ObservableValue getComputedValue(CellDataFeatures param){ + Object rowObject = param.getValue().getValue(); + if(rowObject instanceof RecursiveTreeObject){ + RecursiveTreeObject item = (RecursiveTreeObject) rowObject; + if(item.getGroupedColumn() == this) + return new ReadOnlyObjectWrapper(item.getGroupedValue()); + } + return null; + } + + /** + * @return true if the column is grouped else false + */ + public boolean isGrouped() { + if(getTreeTableView() instanceof JFXTreeTableView && ((JFXTreeTableView)getTreeTableView()).getGroupOrder().contains(this)) + return true; + return false; + } + +} diff --git a/src/com/jfoenix/controls/JFXTreeTableRow.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXTreeTableRow.java similarity index 96% rename from src/com/jfoenix/controls/JFXTreeTableRow.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXTreeTableRow.java index b1eeaead..73924f6f 100644 --- a/src/com/jfoenix/controls/JFXTreeTableRow.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/JFXTreeTableRow.java @@ -1,46 +1,46 @@ -/* - * 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 com.jfoenix.skins.JFXTreeTableRowSkin; -import javafx.scene.control.Skin; -import javafx.scene.control.TreeTableRow; - -/** - * JFXTreeTableRow is the row object used in {@link JFXTreeTableView} - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXTreeTableRow extends TreeTableRow { - /** - * {@inheritDoc} - */ - public JFXTreeTableRow() { - super(); - } - - /** - * {@inheritDoc} - */ - @Override protected Skin createDefaultSkin() { - return new JFXTreeTableRowSkin(this); - } -} +/* + * 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 com.jfoenix.skins.JFXTreeTableRowSkin; +import javafx.scene.control.Skin; +import javafx.scene.control.TreeTableRow; + +/** + * JFXTreeTableRow is the row object used in {@link JFXTreeTableView} + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXTreeTableRow extends TreeTableRow { + /** + * {@inheritDoc} + */ + public JFXTreeTableRow() { + super(); + } + + /** + * {@inheritDoc} + */ + @Override protected Skin createDefaultSkin() { + return new JFXTreeTableRowSkin(this); + } +} diff --git a/src/com/jfoenix/controls/JFXTreeTableView.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXTreeTableView.java similarity index 96% rename from src/com/jfoenix/controls/JFXTreeTableView.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXTreeTableView.java index 86e708b3..fd4b9092 100644 --- a/src/com/jfoenix/controls/JFXTreeTableView.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/JFXTreeTableView.java @@ -1,402 +1,402 @@ -/* - * 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 com.jfoenix.concurrency.JFXUtilities; -import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject; -import com.jfoenix.skins.JFXTreeTableViewSkin; -import javafx.application.Platform; -import javafx.beans.property.IntegerProperty; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleIntegerProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.scene.control.*; -import javafx.scene.input.MouseEvent; -import javafx.util.Callback; - -import java.util.*; -import java.util.concurrent.Semaphore; -import java.util.function.Predicate; - -/** - * JFXTreeTableView is the material design implementation of table view. - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - * DOC: not completed - */ -public class JFXTreeTableView> extends TreeTableView { - - private TreeItem originalRoot; - - /** - * {@inheritDoc} - */ - public JFXTreeTableView() { - super(); - init(); - } - - /** - * {@inheritDoc} - */ - public JFXTreeTableView(TreeItem root) { - super(root); - originalRoot = root; - init(); - } - - /** - * propagate any mouse event on the tree table view to its parent - */ - public void propagateMouseEventsToParent(){ - this.addEventHandler(MouseEvent.ANY, (e)->{ - e.consume(); - this.getParent().fireEvent(e); - }); - } - - /** {@inheritDoc} */ - @Override protected Skin createDefaultSkin() { - return new JFXTreeTableViewSkin(this); - } - - protected void init(){ - this.setRowFactory(new Callback, TreeTableRow>() { - @Override - public TreeTableRow call(TreeTableView param) { - return new JFXTreeTableRow(); - } - }); - - this.getSelectionModel().selectedItemProperty().addListener((o,oldVal,newVal)->{ - if(newVal != null && newVal.getValue() != null) - itemWasSelected = true; - }); - - this.predicate.addListener((o,oldVal,newVal)-> filter(newVal)); - - this.rootProperty().addListener((o,oldVal,newVal)->{ - if(newVal != null){ - setCurrentItemsCount(count(getRoot())); - } - }); - - // compute the current items count - setCurrentItemsCount(count(getRoot())); - - // getGroupOrder().addListener((Change> c) ->{ - // group(); - // }); - } - - @Override - public int getTreeItemLevel(TreeItem node) { - final TreeItem root = getRoot(); - if (node == null) return -1; - if (node == root) return 0; - int level = 0; - TreeItem parent = node.getParent(); - while (parent != null) { - level++; - if (parent == root ) { - break; - } - // handle group nodes - if(parent.getValue() !=null - && parent.getValue() instanceof RecursiveTreeObject - && ((RecursiveTreeObject)parent.getValue()).getGroupedColumn()!=null) - level--; - parent = parent.getParent(); - } - return level; - } - - /* - * clear selection before sorting as its bugged in java - */ - private boolean itemWasSelected = false; - - /** - * {@inheritDoc} - */ - @Override - public void sort(){ - getSelectionModel().clearSelection(); - super.sort(); - if(itemWasSelected) - getSelectionModel().select(0); - } - - - // Allows for multiple column Grouping based on the order of the TreeTableColumns - // in this observableArrayList. - //TODO: treat group order as sort order - private ObservableList> groupOrder = FXCollections.observableArrayList(); - - final ObservableList> getGroupOrder() { - return groupOrder; - } - - // semaphore is used to force mutual exclusion while group/ungroup operation - private Semaphore groupingSemaphore = new Semaphore(1); - - // this method will regroup the treetableview according to columns group order - public void group(TreeTableColumn... treeTableColumns){ - // init groups map - if(groupingSemaphore.tryAcquire()){ - if(groupOrder.size() == 0) groups = new HashMap<>(); - try{ - if(originalRoot == null) originalRoot = getRoot(); - for (TreeTableColumn treeTableColumn : treeTableColumns) - groups = group(treeTableColumn, groups, null, (RecursiveTreeItem) originalRoot); - groupOrder.addAll(treeTableColumns); - // update table ui - buildGroupedRoot(groups, null, 0); - }catch(Exception e){ - e.printStackTrace(); - } - groupingSemaphore.release(); - } - } - - private void refreshGroups(List> groupColumns){ - groups = new HashMap<>(); - for (TreeTableColumn treeTableColumn : groupColumns) - groups = group(treeTableColumn, groups, null, (RecursiveTreeItem) originalRoot); - groupOrder.addAll(groupColumns); - // update table ui - buildGroupedRoot(groups, null, 0); - } - - public void unGroup(TreeTableColumn... treeTableColumns){ - if(groupingSemaphore.tryAcquire()){ - try { - if(groupOrder.size() > 0){ - groupOrder.removeAll(treeTableColumns); - List> grouped = new ArrayList>(); - grouped.addAll(groupOrder); - groupOrder.clear(); - JFXUtilities.runInFXAndWait(()->{ - ArrayList> sortOrder = new ArrayList<>(); - sortOrder.addAll(getSortOrder()); - // needs to reset the children in order to update the parent - List children = Arrays.asList(originalRoot.getChildren().toArray()); - originalRoot.getChildren().clear(); - originalRoot.getChildren().setAll(children); - // reset the original root - setRoot(originalRoot); - getSelectionModel().select(0); - getSortOrder().addAll(sortOrder); - if(grouped.size() != 0) - refreshGroups(grouped); - }); - } - } catch (Exception e) { - e.printStackTrace(); - } - groupingSemaphore.release(); - } - } - - private Map group(TreeTableColumn column, Map parentGroup , Object key, RecursiveTreeItem root){ - if(parentGroup.isEmpty()){ - parentGroup = groupByFunction(root.filteredItems, column); - return parentGroup; - } - Object value = parentGroup.get(key); - if(value instanceof List){ - Object newGroup = groupByFunction((List) value, column); - parentGroup.put(key, newGroup); - return parentGroup; - }else if(value instanceof Map){ - for (Object childKey : ((Map)value).keySet()) - value = group(column,(Map)value,childKey, root); - parentGroup.put(key, value); - return parentGroup; - }else if(key == null){ - for (Object childKey : parentGroup.keySet()) - parentGroup = group(column, parentGroup,childKey, root); - return parentGroup; - } - return parentGroup; - } - - /* - * stream implementation is faster regarding the performance - * however its not compatable when porting to mobile - */ - // protected Map groupByFunction(List> items, TreeTableColumn column){ - // return items.stream().collect(Collectors.groupingBy(child-> column.getCellData((TreeItem)child))); - // } - - protected Map groupByFunction(List> items, TreeTableColumn column){ - Map>> map = new HashMap>>(); - for (TreeItem child : items) { - Object key = column.getCellData(child); - if (map.get(key) == null) { - map.put(key, new ArrayList>()); - } - map.get(key).add(child); - } - return map; - } - - /* - * this method is used to update tree items and set the new root - * after grouping the data model - */ - private void buildGroupedRoot(Map groupedItems, RecursiveTreeItem parent , int groupIndex){ - boolean setRoot = false; - if(parent == null){ - parent = new RecursiveTreeItem<>(new RecursiveTreeObject(), RecursiveTreeObject::getChildren); - setRoot = true; - } - - for(Object key : groupedItems.keySet()){ - RecursiveTreeObject groupItem = new RecursiveTreeObject<>(); - groupItem.setGroupedValue(key); - groupItem.setGroupedColumn(groupOrder.get(groupIndex)); - - RecursiveTreeItem node = new RecursiveTreeItem<>(groupItem, RecursiveTreeObject::getChildren); - // TODO: need to be removed once the selection issue is fixed - node.expandedProperty().addListener((o,oldVal,newVal)->{ - getSelectionModel().clearSelection(); - }); - - parent.originalItems.add(node); - parent.getChildren().add(node); - - Object children = groupedItems.get(key); - if(children instanceof List){ - node.originalItems.addAll((List)children); - node.getChildren().addAll((List)children); - }else if(children instanceof Map){ - buildGroupedRoot((Map)children, node, groupIndex+1); - } - } - - // update ui - if(setRoot){ - final RecursiveTreeItem newParent = parent; - JFXUtilities.runInFX(()->{ - ArrayList> sortOrder = new ArrayList<>(); - sortOrder.addAll(getSortOrder()); - setRoot(newParent); - getSortOrder().addAll(sortOrder); - getSelectionModel().select(0); - }); - } - } - - - /* - * this method will filter the treetable and it - */ - - private Timer t; - - private final void filter(Predicate> predicate){ - if(originalRoot == null) originalRoot = getRoot(); - if(t!=null){ - t.cancel(); - t.purge(); - } - t = new Timer(); - t.schedule(new TimerTask() { - @Override - public void run() { - /* - * filter the original root and regroup the data - */ - new Thread(()->{ - // filter the ungrouped root - ((RecursiveTreeItem) originalRoot).setPredicate(predicate); - // regroup the data - reGroup(); - Platform.runLater(()->{ - getSelectionModel().select(0); - setCurrentItemsCount(count(getRoot())); - }); - }).start(); - } - }, 500); - } - - public void reGroup() { - if(!groupOrder.isEmpty()){ - ArrayList> tempGroups = new ArrayList<>(groupOrder); - groupOrder.clear(); - group(tempGroups.toArray(new TreeTableColumn[tempGroups.size()])); - } - } - - private ObjectProperty>> predicate = new SimpleObjectProperty>>((TreeItem t) -> true); - - public final ObjectProperty>> predicateProperty() { - return this.predicate; - } - - public final Predicate> getPredicate() { - return this.predicateProperty().get(); - } - - public final void setPredicate(final Predicate> predicate) { - this.predicateProperty().set(predicate); - } - - private IntegerProperty currentItemsCount = new SimpleIntegerProperty(0); - private Map> groups; - - /** - * @return the initial tree items count ( add / remove items should be handled manually for now ) - */ - public final IntegerProperty currentItemsCountProperty() { - return this.currentItemsCount; - } - - /** - * @return the initial tree items count ( add / remove items should be handled manually for now ) - */ - public final int getCurrentItemsCount() { - return this.currentItemsCountProperty().get(); - } - - /** - * sets the current items count - * @param currentItemsCount - */ - public final void setCurrentItemsCount(final int currentItemsCount) { - this.currentItemsCountProperty().set(currentItemsCount); - } - - private int count(TreeItem node){ - if(node == null ) return 0; - - int count = 1; - if(node.getValue() == null || (node.getValue() != null && node.getValue().getClass().equals(RecursiveTreeObject.class))) count = 0; - for (TreeItem child : node.getChildren()) { - count += count(child); - } - return count; - } -} +/* + * 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 com.jfoenix.concurrency.JFXUtilities; +import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject; +import com.jfoenix.skins.JFXTreeTableViewSkin; +import javafx.application.Platform; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.scene.control.*; +import javafx.scene.input.MouseEvent; +import javafx.util.Callback; + +import java.util.*; +import java.util.concurrent.Semaphore; +import java.util.function.Predicate; + +/** + * JFXTreeTableView is the material design implementation of table view. + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + * DOC: not completed + */ +public class JFXTreeTableView> extends TreeTableView { + + private TreeItem originalRoot; + + /** + * {@inheritDoc} + */ + public JFXTreeTableView() { + super(); + init(); + } + + /** + * {@inheritDoc} + */ + public JFXTreeTableView(TreeItem root) { + super(root); + originalRoot = root; + init(); + } + + /** + * propagate any mouse event on the tree table view to its parent + */ + public void propagateMouseEventsToParent(){ + this.addEventHandler(MouseEvent.ANY, (e)->{ + e.consume(); + this.getParent().fireEvent(e); + }); + } + + /** {@inheritDoc} */ + @Override protected Skin createDefaultSkin() { + return new JFXTreeTableViewSkin(this); + } + + protected void init(){ + this.setRowFactory(new Callback, TreeTableRow>() { + @Override + public TreeTableRow call(TreeTableView param) { + return new JFXTreeTableRow(); + } + }); + + this.getSelectionModel().selectedItemProperty().addListener((o,oldVal,newVal)->{ + if(newVal != null && newVal.getValue() != null) + itemWasSelected = true; + }); + + this.predicate.addListener((o,oldVal,newVal)-> filter(newVal)); + + this.rootProperty().addListener((o,oldVal,newVal)->{ + if(newVal != null){ + setCurrentItemsCount(count(getRoot())); + } + }); + + // compute the current items count + setCurrentItemsCount(count(getRoot())); + + // getGroupOrder().addListener((Change> c) ->{ + // group(); + // }); + } + + @Override + public int getTreeItemLevel(TreeItem node) { + final TreeItem root = getRoot(); + if (node == null) return -1; + if (node == root) return 0; + int level = 0; + TreeItem parent = node.getParent(); + while (parent != null) { + level++; + if (parent == root ) { + break; + } + // handle group nodes + if(parent.getValue() !=null + && parent.getValue() instanceof RecursiveTreeObject + && ((RecursiveTreeObject)parent.getValue()).getGroupedColumn()!=null) + level--; + parent = parent.getParent(); + } + return level; + } + + /* + * clear selection before sorting as its bugged in java + */ + private boolean itemWasSelected = false; + + /** + * {@inheritDoc} + */ + @Override + public void sort(){ + getSelectionModel().clearSelection(); + super.sort(); + if(itemWasSelected) + getSelectionModel().select(0); + } + + + // Allows for multiple column Grouping based on the order of the TreeTableColumns + // in this observableArrayList. + //TODO: treat group order as sort order + private ObservableList> groupOrder = FXCollections.observableArrayList(); + + final ObservableList> getGroupOrder() { + return groupOrder; + } + + // semaphore is used to force mutual exclusion while group/ungroup operation + private Semaphore groupingSemaphore = new Semaphore(1); + + // this method will regroup the treetableview according to columns group order + public void group(TreeTableColumn... treeTableColumns){ + // init groups map + if(groupingSemaphore.tryAcquire()){ + if(groupOrder.size() == 0) groups = new HashMap<>(); + try{ + if(originalRoot == null) originalRoot = getRoot(); + for (TreeTableColumn treeTableColumn : treeTableColumns) + groups = group(treeTableColumn, groups, null, (RecursiveTreeItem) originalRoot); + groupOrder.addAll(treeTableColumns); + // update table ui + buildGroupedRoot(groups, null, 0); + }catch(Exception e){ + e.printStackTrace(); + } + groupingSemaphore.release(); + } + } + + private void refreshGroups(List> groupColumns){ + groups = new HashMap<>(); + for (TreeTableColumn treeTableColumn : groupColumns) + groups = group(treeTableColumn, groups, null, (RecursiveTreeItem) originalRoot); + groupOrder.addAll(groupColumns); + // update table ui + buildGroupedRoot(groups, null, 0); + } + + public void unGroup(TreeTableColumn... treeTableColumns){ + if(groupingSemaphore.tryAcquire()){ + try { + if(groupOrder.size() > 0){ + groupOrder.removeAll(treeTableColumns); + List> grouped = new ArrayList>(); + grouped.addAll(groupOrder); + groupOrder.clear(); + JFXUtilities.runInFXAndWait(()->{ + ArrayList> sortOrder = new ArrayList<>(); + sortOrder.addAll(getSortOrder()); + // needs to reset the children in order to update the parent + List children = Arrays.asList(originalRoot.getChildren().toArray()); + originalRoot.getChildren().clear(); + originalRoot.getChildren().setAll(children); + // reset the original root + setRoot(originalRoot); + getSelectionModel().select(0); + getSortOrder().addAll(sortOrder); + if(grouped.size() != 0) + refreshGroups(grouped); + }); + } + } catch (Exception e) { + e.printStackTrace(); + } + groupingSemaphore.release(); + } + } + + private Map group(TreeTableColumn column, Map parentGroup , Object key, RecursiveTreeItem root){ + if(parentGroup.isEmpty()){ + parentGroup = groupByFunction(root.filteredItems, column); + return parentGroup; + } + Object value = parentGroup.get(key); + if(value instanceof List){ + Object newGroup = groupByFunction((List) value, column); + parentGroup.put(key, newGroup); + return parentGroup; + }else if(value instanceof Map){ + for (Object childKey : ((Map)value).keySet()) + value = group(column,(Map)value,childKey, root); + parentGroup.put(key, value); + return parentGroup; + }else if(key == null){ + for (Object childKey : parentGroup.keySet()) + parentGroup = group(column, parentGroup,childKey, root); + return parentGroup; + } + return parentGroup; + } + + /* + * stream implementation is faster regarding the performance + * however its not compatable when porting to mobile + */ + // protected Map groupByFunction(List> items, TreeTableColumn column){ + // return items.stream().collect(Collectors.groupingBy(child-> column.getCellData((TreeItem)child))); + // } + + protected Map groupByFunction(List> items, TreeTableColumn column){ + Map>> map = new HashMap>>(); + for (TreeItem child : items) { + Object key = column.getCellData(child); + if (map.get(key) == null) { + map.put(key, new ArrayList>()); + } + map.get(key).add(child); + } + return map; + } + + /* + * this method is used to update tree items and set the new root + * after grouping the data model + */ + private void buildGroupedRoot(Map groupedItems, RecursiveTreeItem parent , int groupIndex){ + boolean setRoot = false; + if(parent == null){ + parent = new RecursiveTreeItem<>(new RecursiveTreeObject(), RecursiveTreeObject::getChildren); + setRoot = true; + } + + for(Object key : groupedItems.keySet()){ + RecursiveTreeObject groupItem = new RecursiveTreeObject<>(); + groupItem.setGroupedValue(key); + groupItem.setGroupedColumn(groupOrder.get(groupIndex)); + + RecursiveTreeItem node = new RecursiveTreeItem<>(groupItem, RecursiveTreeObject::getChildren); + // TODO: need to be removed once the selection issue is fixed + node.expandedProperty().addListener((o,oldVal,newVal)->{ + getSelectionModel().clearSelection(); + }); + + parent.originalItems.add(node); + parent.getChildren().add(node); + + Object children = groupedItems.get(key); + if(children instanceof List){ + node.originalItems.addAll((List)children); + node.getChildren().addAll((List)children); + }else if(children instanceof Map){ + buildGroupedRoot((Map)children, node, groupIndex+1); + } + } + + // update ui + if(setRoot){ + final RecursiveTreeItem newParent = parent; + JFXUtilities.runInFX(()->{ + ArrayList> sortOrder = new ArrayList<>(); + sortOrder.addAll(getSortOrder()); + setRoot(newParent); + getSortOrder().addAll(sortOrder); + getSelectionModel().select(0); + }); + } + } + + + /* + * this method will filter the treetable and it + */ + + private Timer t; + + private final void filter(Predicate> predicate){ + if(originalRoot == null) originalRoot = getRoot(); + if(t!=null){ + t.cancel(); + t.purge(); + } + t = new Timer(); + t.schedule(new TimerTask() { + @Override + public void run() { + /* + * filter the original root and regroup the data + */ + new Thread(()->{ + // filter the ungrouped root + ((RecursiveTreeItem) originalRoot).setPredicate(predicate); + // regroup the data + reGroup(); + Platform.runLater(()->{ + getSelectionModel().select(0); + setCurrentItemsCount(count(getRoot())); + }); + }).start(); + } + }, 500); + } + + public void reGroup() { + if(!groupOrder.isEmpty()){ + ArrayList> tempGroups = new ArrayList<>(groupOrder); + groupOrder.clear(); + group(tempGroups.toArray(new TreeTableColumn[tempGroups.size()])); + } + } + + private ObjectProperty>> predicate = new SimpleObjectProperty>>((TreeItem t) -> true); + + public final ObjectProperty>> predicateProperty() { + return this.predicate; + } + + public final Predicate> getPredicate() { + return this.predicateProperty().get(); + } + + public final void setPredicate(final Predicate> predicate) { + this.predicateProperty().set(predicate); + } + + private IntegerProperty currentItemsCount = new SimpleIntegerProperty(0); + private Map> groups; + + /** + * @return the initial tree items count ( add / remove items should be handled manually for now ) + */ + public final IntegerProperty currentItemsCountProperty() { + return this.currentItemsCount; + } + + /** + * @return the initial tree items count ( add / remove items should be handled manually for now ) + */ + public final int getCurrentItemsCount() { + return this.currentItemsCountProperty().get(); + } + + /** + * sets the current items count + * @param currentItemsCount + */ + public final void setCurrentItemsCount(final int currentItemsCount) { + this.currentItemsCountProperty().set(currentItemsCount); + } + + private int count(TreeItem node){ + if(node == null ) return 0; + + int count = 1; + if(node.getValue() == null || (node.getValue() != null && node.getValue().getClass().equals(RecursiveTreeObject.class))) count = 0; + for (TreeItem child : node.getChildren()) { + count += count(child); + } + return count; + } +} diff --git a/src/com/jfoenix/controls/JFXTreeView.java b/jfoenix/src/main/java/com/jfoenix/controls/JFXTreeView.java similarity index 100% rename from src/com/jfoenix/controls/JFXTreeView.java rename to jfoenix/src/main/java/com/jfoenix/controls/JFXTreeView.java diff --git a/src/com/jfoenix/controls/RecursiveTreeItem.java b/jfoenix/src/main/java/com/jfoenix/controls/RecursiveTreeItem.java similarity index 97% rename from src/com/jfoenix/controls/RecursiveTreeItem.java rename to jfoenix/src/main/java/com/jfoenix/controls/RecursiveTreeItem.java index 91dff1f2..e166f629 100644 --- a/src/com/jfoenix/controls/RecursiveTreeItem.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/RecursiveTreeItem.java @@ -1,217 +1,217 @@ -/* - * 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 com.jfoenix.concurrency.JFXUtilities; -import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject; -import javafx.beans.binding.Bindings; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.collections.FXCollections; -import javafx.collections.ListChangeListener; -import javafx.collections.ObservableList; -import javafx.collections.transformation.FilteredList; -import javafx.scene.Node; -import javafx.scene.control.TreeItem; -import javafx.util.Callback; - -import java.util.function.Predicate; - -/** - * RecursiveTreeItem is used along with RecursiveTreeObject - * to build the data model for the TreeTableView. - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class RecursiveTreeItem> extends TreeItem { - - private Callback, ObservableList> childrenFactory; - - /** - * predicate used to filter nodes - */ - private ObjectProperty>> predicate = new SimpleObjectProperty>>((TreeItem t) -> true); - - /** - * list of original items - */ - ObservableList> originalItems = FXCollections.observableArrayList(); - - /** - * list of filtered items - */ - FilteredList> filteredItems ; - - /*************************************************************************** - * * - * Constructors * - * * - **************************************************************************/ - - /** - * creates empty recursive tree item - * - * @param func is the callback used to retrieve the children of the current tree item - */ - public RecursiveTreeItem(Callback, ObservableList> func) { - this(null, (Node) null, func); - } - - /** - * creates recursive tree item for a specified value - * - * @param value of the tree item - * @param func is the callback used to retrieve the children of the current tree item - */ - public RecursiveTreeItem(final T value, Callback, ObservableList> func) { - this(value, (Node) null, func); - } - - /** - * creates recursive tree item for a specified value and a graphic node - * - * @param value of the tree item - * @param graphic node - * @param func is the callback used to retrieve the children of the current tree item - */ - public RecursiveTreeItem(final T value, Node graphic, Callback, ObservableList> func) { - super(value, graphic); - this.childrenFactory = func; - init(value); - } - - /** - * creates recursive tree item from a data list - * - * @param dataList of values - * @param func is the callback used to retrieve the children of the current tree item - */ - public RecursiveTreeItem(ObservableList dataList, Callback, ObservableList> func) { - RecursiveTreeObject root = new RecursiveTreeObject<>(); - root.setChildren(dataList); - this.childrenFactory = func; - init(root); - } - - private void init(RecursiveTreeObject value){ - - if (value != null) { - addChildrenListener(value); - } - valueProperty().addListener((o, oldValue, newValue) -> { - if (newValue != null) { - addChildrenListener(newValue); - } - }); - - this.filteredItems.predicateProperty().bind(Bindings.createObjectBinding(() -> { - Predicate> newPredicate = new Predicate>() { - @Override - public boolean test(TreeItem child) { - // Set the predicate of child items to force filtering - if (child instanceof RecursiveTreeItem) { - if(!((RecursiveTreeItem)child).originalItems.isEmpty()){ - RecursiveTreeItem filterableChild = (RecursiveTreeItem) child; - filterableChild.setPredicate(RecursiveTreeItem.this.predicate.get()); - } - } - // If there is no predicate, keep this tree item - if (RecursiveTreeItem.this.predicate.get() == null) - return true; - // If there are children, keep this tree item - if (child.getChildren().size() > 0) - return true; - // If its a group node keep this item if it has children - if (child.getValue() instanceof RecursiveTreeObject && child.getValue().getClass() == RecursiveTreeObject.class){ - if(child.getChildren().size() == 0) - return false; - return true; - } - // Otherwise ask the TreeItemPredicate - return RecursiveTreeItem.this.predicate.get().test(child); - } - }; - return newPredicate; - }, this.predicate)); - - - this.filteredItems.predicateProperty().addListener((o,oldVal,newVal)->{ - JFXUtilities.runInFXAndWait(()->{ - getChildren().clear(); - getChildren().addAll(filteredItems); - }); - }); - } - - - private void addChildrenListener(RecursiveTreeObject value) { - final ObservableList children = childrenFactory.call(value); - originalItems = FXCollections.observableArrayList(); - for(T child : children) - originalItems.add(new RecursiveTreeItem<>(child, getGraphic(), childrenFactory)); - - filteredItems = new FilteredList<>(originalItems, (TreeItem t) -> true); - - this.getChildren().addAll(originalItems); - - children.addListener((ListChangeListener) change -> { - while (change.next()) { - if (change.wasAdded()) { - change.getAddedSubList().forEach(t -> { - RecursiveTreeItem newItem = new RecursiveTreeItem<>(t, getGraphic(), childrenFactory); - RecursiveTreeItem.this.getChildren().add(newItem); - originalItems.add(newItem); - }); - } - if (change.wasRemoved()) { - change.getRemoved().forEach(t -> { - for(int i = 0 ; i < RecursiveTreeItem.this.getChildren().size(); i++){ - if(this.getChildren().get(i).getValue().equals(t)){ - // remove the items from the current/original items list - originalItems.remove(this.getChildren().remove(i)); - i--; - } - } - // final List> itemsToRemove = RecursiveTreeItem.this.getChildren().stream() - // .filter(treeItem -> treeItem.getValue().equals(t)).collect(Collectors.toList()); - // RecursiveTreeItem.this.getChildren().removeAll(itemsToRemove); - }); - } - } - }); - - } - - public final ObjectProperty>> predicateProperty() { - return this.predicate; - } - - public final Predicate> getPredicate() { - return this.predicateProperty().get(); - } - - public final void setPredicate(final Predicate> predicate) { - this.predicateProperty().set(predicate); - } - - - +/* + * 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 com.jfoenix.concurrency.JFXUtilities; +import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject; +import javafx.beans.binding.Bindings; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; +import javafx.scene.Node; +import javafx.scene.control.TreeItem; +import javafx.util.Callback; + +import java.util.function.Predicate; + +/** + * RecursiveTreeItem is used along with RecursiveTreeObject + * to build the data model for the TreeTableView. + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class RecursiveTreeItem> extends TreeItem { + + private Callback, ObservableList> childrenFactory; + + /** + * predicate used to filter nodes + */ + private ObjectProperty>> predicate = new SimpleObjectProperty>>((TreeItem t) -> true); + + /** + * list of original items + */ + ObservableList> originalItems = FXCollections.observableArrayList(); + + /** + * list of filtered items + */ + FilteredList> filteredItems ; + + /*************************************************************************** + * * + * Constructors * + * * + **************************************************************************/ + + /** + * creates empty recursive tree item + * + * @param func is the callback used to retrieve the children of the current tree item + */ + public RecursiveTreeItem(Callback, ObservableList> func) { + this(null, (Node) null, func); + } + + /** + * creates recursive tree item for a specified value + * + * @param value of the tree item + * @param func is the callback used to retrieve the children of the current tree item + */ + public RecursiveTreeItem(final T value, Callback, ObservableList> func) { + this(value, (Node) null, func); + } + + /** + * creates recursive tree item for a specified value and a graphic node + * + * @param value of the tree item + * @param graphic node + * @param func is the callback used to retrieve the children of the current tree item + */ + public RecursiveTreeItem(final T value, Node graphic, Callback, ObservableList> func) { + super(value, graphic); + this.childrenFactory = func; + init(value); + } + + /** + * creates recursive tree item from a data list + * + * @param dataList of values + * @param func is the callback used to retrieve the children of the current tree item + */ + public RecursiveTreeItem(ObservableList dataList, Callback, ObservableList> func) { + RecursiveTreeObject root = new RecursiveTreeObject<>(); + root.setChildren(dataList); + this.childrenFactory = func; + init(root); + } + + private void init(RecursiveTreeObject value){ + + if (value != null) { + addChildrenListener(value); + } + valueProperty().addListener((o, oldValue, newValue) -> { + if (newValue != null) { + addChildrenListener(newValue); + } + }); + + this.filteredItems.predicateProperty().bind(Bindings.createObjectBinding(() -> { + Predicate> newPredicate = new Predicate>() { + @Override + public boolean test(TreeItem child) { + // Set the predicate of child items to force filtering + if (child instanceof RecursiveTreeItem) { + if(!((RecursiveTreeItem)child).originalItems.isEmpty()){ + RecursiveTreeItem filterableChild = (RecursiveTreeItem) child; + filterableChild.setPredicate(RecursiveTreeItem.this.predicate.get()); + } + } + // If there is no predicate, keep this tree item + if (RecursiveTreeItem.this.predicate.get() == null) + return true; + // If there are children, keep this tree item + if (child.getChildren().size() > 0) + return true; + // If its a group node keep this item if it has children + if (child.getValue() instanceof RecursiveTreeObject && child.getValue().getClass() == RecursiveTreeObject.class){ + if(child.getChildren().size() == 0) + return false; + return true; + } + // Otherwise ask the TreeItemPredicate + return RecursiveTreeItem.this.predicate.get().test(child); + } + }; + return newPredicate; + }, this.predicate)); + + + this.filteredItems.predicateProperty().addListener((o,oldVal,newVal)->{ + JFXUtilities.runInFXAndWait(()->{ + getChildren().clear(); + getChildren().addAll(filteredItems); + }); + }); + } + + + private void addChildrenListener(RecursiveTreeObject value) { + final ObservableList children = childrenFactory.call(value); + originalItems = FXCollections.observableArrayList(); + for(T child : children) + originalItems.add(new RecursiveTreeItem<>(child, getGraphic(), childrenFactory)); + + filteredItems = new FilteredList<>(originalItems, (TreeItem t) -> true); + + this.getChildren().addAll(originalItems); + + children.addListener((ListChangeListener) change -> { + while (change.next()) { + if (change.wasAdded()) { + change.getAddedSubList().forEach(t -> { + RecursiveTreeItem newItem = new RecursiveTreeItem<>(t, getGraphic(), childrenFactory); + RecursiveTreeItem.this.getChildren().add(newItem); + originalItems.add(newItem); + }); + } + if (change.wasRemoved()) { + change.getRemoved().forEach(t -> { + for(int i = 0 ; i < RecursiveTreeItem.this.getChildren().size(); i++){ + if(this.getChildren().get(i).getValue().equals(t)){ + // remove the items from the current/original items list + originalItems.remove(this.getChildren().remove(i)); + i--; + } + } + // final List> itemsToRemove = RecursiveTreeItem.this.getChildren().stream() + // .filter(treeItem -> treeItem.getValue().equals(t)).collect(Collectors.toList()); + // RecursiveTreeItem.this.getChildren().removeAll(itemsToRemove); + }); + } + } + }); + + } + + public final ObjectProperty>> predicateProperty() { + return this.predicate; + } + + public final Predicate> getPredicate() { + return this.predicateProperty().get(); + } + + public final void setPredicate(final Predicate> predicate) { + this.predicateProperty().set(predicate); + } + + + } \ No newline at end of file diff --git a/src/com/jfoenix/controls/behavior/JFXColorPickerBehavior.java b/jfoenix/src/main/java/com/jfoenix/controls/behavior/JFXColorPickerBehavior.java similarity index 97% rename from src/com/jfoenix/controls/behavior/JFXColorPickerBehavior.java rename to jfoenix/src/main/java/com/jfoenix/controls/behavior/JFXColorPickerBehavior.java index 09ed2c33..860f3e68 100644 --- a/src/com/jfoenix/controls/behavior/JFXColorPickerBehavior.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/behavior/JFXColorPickerBehavior.java @@ -1,83 +1,83 @@ -/* - * 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.behavior; - -import com.jfoenix.skins.JFXColorPickerSkin; -import com.sun.javafx.scene.control.behavior.ComboBoxBaseBehavior; -import com.sun.javafx.scene.control.behavior.KeyBinding; -import javafx.scene.control.ColorPicker; -import javafx.scene.paint.Color; - -import java.util.ArrayList; -import java.util.List; - -import static javafx.scene.input.KeyCode.*; -import static javafx.scene.input.KeyEvent.KEY_PRESSED; - -/** - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXColorPickerBehavior extends ComboBoxBaseBehavior { - - /*************************************************************************** - * * - * Constructors * - * * - **************************************************************************/ - - public JFXColorPickerBehavior(final ColorPicker colorPicker) { - super(colorPicker, JFX_COLOR_PICKER_BINDINGS); - } - - /*************************************************************************** - * * - * Key event handling * - * * - **************************************************************************/ - protected static final String JFX_OPEN_ACTION = "Open"; - protected static final String JFX_CLOSE_ACTION = "Close"; - protected static final List JFX_COLOR_PICKER_BINDINGS = new ArrayList(); - static { - JFX_COLOR_PICKER_BINDINGS.add(new KeyBinding(ESCAPE, KEY_PRESSED, JFX_CLOSE_ACTION)); - JFX_COLOR_PICKER_BINDINGS.add(new KeyBinding(SPACE, KEY_PRESSED, JFX_OPEN_ACTION)); - JFX_COLOR_PICKER_BINDINGS.add(new KeyBinding(ENTER, KEY_PRESSED, JFX_OPEN_ACTION)); - } - - @Override protected void callAction(String name) { - if (JFX_OPEN_ACTION.equals(name)) show(); - else if(JFX_CLOSE_ACTION.equals(name)) hide(); - else super.callAction(name); - } - - /************************************************************************** - * * - * Mouse Events handling (when losing focus) * - * * - *************************************************************************/ - - @Override public void onAutoHide() { - ColorPicker colorPicker = (ColorPicker)getControl(); - JFXColorPickerSkin cpSkin = (JFXColorPickerSkin)colorPicker.getSkin(); - cpSkin.syncWithAutoUpdate(); - if (!colorPicker.isShowing()) super.onAutoHide(); - } - -} +/* + * 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.behavior; + +import com.jfoenix.skins.JFXColorPickerSkin; +import com.sun.javafx.scene.control.behavior.ComboBoxBaseBehavior; +import com.sun.javafx.scene.control.behavior.KeyBinding; +import javafx.scene.control.ColorPicker; +import javafx.scene.paint.Color; + +import java.util.ArrayList; +import java.util.List; + +import static javafx.scene.input.KeyCode.*; +import static javafx.scene.input.KeyEvent.KEY_PRESSED; + +/** + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXColorPickerBehavior extends ComboBoxBaseBehavior { + + /*************************************************************************** + * * + * Constructors * + * * + **************************************************************************/ + + public JFXColorPickerBehavior(final ColorPicker colorPicker) { + super(colorPicker, JFX_COLOR_PICKER_BINDINGS); + } + + /*************************************************************************** + * * + * Key event handling * + * * + **************************************************************************/ + protected static final String JFX_OPEN_ACTION = "Open"; + protected static final String JFX_CLOSE_ACTION = "Close"; + protected static final List JFX_COLOR_PICKER_BINDINGS = new ArrayList(); + static { + JFX_COLOR_PICKER_BINDINGS.add(new KeyBinding(ESCAPE, KEY_PRESSED, JFX_CLOSE_ACTION)); + JFX_COLOR_PICKER_BINDINGS.add(new KeyBinding(SPACE, KEY_PRESSED, JFX_OPEN_ACTION)); + JFX_COLOR_PICKER_BINDINGS.add(new KeyBinding(ENTER, KEY_PRESSED, JFX_OPEN_ACTION)); + } + + @Override protected void callAction(String name) { + if (JFX_OPEN_ACTION.equals(name)) show(); + else if(JFX_CLOSE_ACTION.equals(name)) hide(); + else super.callAction(name); + } + + /************************************************************************** + * * + * Mouse Events handling (when losing focus) * + * * + *************************************************************************/ + + @Override public void onAutoHide() { + ColorPicker colorPicker = (ColorPicker)getControl(); + JFXColorPickerSkin cpSkin = (JFXColorPickerSkin)colorPicker.getSkin(); + cpSkin.syncWithAutoUpdate(); + if (!colorPicker.isShowing()) super.onAutoHide(); + } + +} diff --git a/src/com/jfoenix/controls/behavior/JFXDatePickerBehavior.java b/jfoenix/src/main/java/com/jfoenix/controls/behavior/JFXDatePickerBehavior.java similarity index 97% rename from src/com/jfoenix/controls/behavior/JFXDatePickerBehavior.java rename to jfoenix/src/main/java/com/jfoenix/controls/behavior/JFXDatePickerBehavior.java index d7348d29..5b629ff5 100644 --- a/src/com/jfoenix/controls/behavior/JFXDatePickerBehavior.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/behavior/JFXDatePickerBehavior.java @@ -1,71 +1,71 @@ -/* - * 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.behavior; - -import com.jfoenix.skins.JFXDatePickerSkin; -import com.sun.javafx.scene.control.behavior.ComboBoxBaseBehavior; -import com.sun.javafx.scene.control.behavior.KeyBinding; -import javafx.scene.control.DatePicker; - -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.List; - -/** - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXDatePickerBehavior extends ComboBoxBaseBehavior { - - /*************************************************************************** - * * - * Constructors * - * * - **************************************************************************/ - - public JFXDatePickerBehavior(final DatePicker datePicker) { - super(datePicker, JFX_DATE_PICKER_BINDINGS); - } - - /*************************************************************************** - * * - * Key event handling * - * * - **************************************************************************/ - - protected static final List JFX_DATE_PICKER_BINDINGS = new ArrayList(); - static { - JFX_DATE_PICKER_BINDINGS.addAll(COMBO_BOX_BASE_BINDINGS); - } - - /************************************************************************** - * * - * Mouse Events handling (when losing focus) * - * * - *************************************************************************/ - - @Override public void onAutoHide() { - DatePicker datePicker = (DatePicker)getControl(); - JFXDatePickerSkin cpSkin = (JFXDatePickerSkin)datePicker.getSkin(); - cpSkin.syncWithAutoUpdate(); - if (!datePicker.isShowing()) super.onAutoHide(); - } - -} +/* + * 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.behavior; + +import com.jfoenix.skins.JFXDatePickerSkin; +import com.sun.javafx.scene.control.behavior.ComboBoxBaseBehavior; +import com.sun.javafx.scene.control.behavior.KeyBinding; +import javafx.scene.control.DatePicker; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXDatePickerBehavior extends ComboBoxBaseBehavior { + + /*************************************************************************** + * * + * Constructors * + * * + **************************************************************************/ + + public JFXDatePickerBehavior(final DatePicker datePicker) { + super(datePicker, JFX_DATE_PICKER_BINDINGS); + } + + /*************************************************************************** + * * + * Key event handling * + * * + **************************************************************************/ + + protected static final List JFX_DATE_PICKER_BINDINGS = new ArrayList(); + static { + JFX_DATE_PICKER_BINDINGS.addAll(COMBO_BOX_BASE_BINDINGS); + } + + /************************************************************************** + * * + * Mouse Events handling (when losing focus) * + * * + *************************************************************************/ + + @Override public void onAutoHide() { + DatePicker datePicker = (DatePicker)getControl(); + JFXDatePickerSkin cpSkin = (JFXDatePickerSkin)datePicker.getSkin(); + cpSkin.syncWithAutoUpdate(); + if (!datePicker.isShowing()) super.onAutoHide(); + } + +} diff --git a/src/com/jfoenix/controls/behavior/JFXTimePickerBehavior.java b/jfoenix/src/main/java/com/jfoenix/controls/behavior/JFXTimePickerBehavior.java similarity index 100% rename from src/com/jfoenix/controls/behavior/JFXTimePickerBehavior.java rename to jfoenix/src/main/java/com/jfoenix/controls/behavior/JFXTimePickerBehavior.java diff --git a/src/com/jfoenix/controls/behavior/JFXTreeTableCellBehavior.java b/jfoenix/src/main/java/com/jfoenix/controls/behavior/JFXTreeTableCellBehavior.java similarity index 97% rename from src/com/jfoenix/controls/behavior/JFXTreeTableCellBehavior.java rename to jfoenix/src/main/java/com/jfoenix/controls/behavior/JFXTreeTableCellBehavior.java index cbbf96bb..936b6196 100644 --- a/src/com/jfoenix/controls/behavior/JFXTreeTableCellBehavior.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/behavior/JFXTreeTableCellBehavior.java @@ -1,52 +1,52 @@ -/* - * 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.behavior; - -import com.sun.javafx.scene.control.behavior.TreeTableCellBehavior; -import javafx.scene.Node; -import javafx.scene.control.TreeItem; -import javafx.scene.control.TreeTableCell; - -/** - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXTreeTableCellBehavior extends TreeTableCellBehavior{ - - public JFXTreeTableCellBehavior(TreeTableCell control) { - super(control); - } - - @Override protected boolean handleDisclosureNode(double x, double y) { - final TreeItem treeItem = getControl().getTreeTableRow().getTreeItem(); - if(!treeItem.isLeaf()){ - final Node disclosureNode = getControl().getTreeTableRow().getDisclosureNode(); - if (disclosureNode != null) { - if (disclosureNode.getBoundsInParent().contains(x+disclosureNode.getTranslateX(), y)) { - if (treeItem != null) { - treeItem.setExpanded(!treeItem.isExpanded()); - } - return true; - } - } - } - return 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.controls.behavior; + +import com.sun.javafx.scene.control.behavior.TreeTableCellBehavior; +import javafx.scene.Node; +import javafx.scene.control.TreeItem; +import javafx.scene.control.TreeTableCell; + +/** + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXTreeTableCellBehavior extends TreeTableCellBehavior{ + + public JFXTreeTableCellBehavior(TreeTableCell control) { + super(control); + } + + @Override protected boolean handleDisclosureNode(double x, double y) { + final TreeItem treeItem = getControl().getTreeTableRow().getTreeItem(); + if(!treeItem.isLeaf()){ + final Node disclosureNode = getControl().getTreeTableRow().getDisclosureNode(); + if (disclosureNode != null) { + if (disclosureNode.getBoundsInParent().contains(x+disclosureNode.getTranslateX(), y)) { + if (treeItem != null) { + treeItem.setExpanded(!treeItem.isExpanded()); + } + return true; + } + } + } + return false; + } +} diff --git a/src/com/jfoenix/controls/cells/editors/DoubleTextFieldEditorBuilder.java b/jfoenix/src/main/java/com/jfoenix/controls/cells/editors/DoubleTextFieldEditorBuilder.java similarity index 100% rename from src/com/jfoenix/controls/cells/editors/DoubleTextFieldEditorBuilder.java rename to jfoenix/src/main/java/com/jfoenix/controls/cells/editors/DoubleTextFieldEditorBuilder.java diff --git a/src/com/jfoenix/controls/cells/editors/IntegerTextFieldEditorBuilder.java b/jfoenix/src/main/java/com/jfoenix/controls/cells/editors/IntegerTextFieldEditorBuilder.java similarity index 96% rename from src/com/jfoenix/controls/cells/editors/IntegerTextFieldEditorBuilder.java rename to jfoenix/src/main/java/com/jfoenix/controls/cells/editors/IntegerTextFieldEditorBuilder.java index dc655979..42221928 100644 --- a/src/com/jfoenix/controls/cells/editors/IntegerTextFieldEditorBuilder.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/cells/editors/IntegerTextFieldEditorBuilder.java @@ -1,96 +1,96 @@ -/* - * 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.cells.editors; - -import com.jfoenix.controls.JFXTextField; -import com.jfoenix.controls.cells.editors.base.EditorNodeBuilder; -import com.jfoenix.validation.NumberValidator; -import javafx.application.Platform; -import javafx.beans.binding.DoubleBinding; -import javafx.beans.value.ChangeListener; -import javafx.event.EventHandler; -import javafx.scene.input.KeyEvent; -import javafx.scene.layout.Region; -import javafx.scene.layout.StackPane; - -/** - *

Text field cell editor (numbers only)

- * this an example of the cell editor, it creates a JFXTextField node to - * allow the user to edit the cell value - *

- * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class IntegerTextFieldEditorBuilder implements EditorNodeBuilder { - - private JFXTextField textField; - - @Override - public void startEdit() { - Platform.runLater(()->{ - textField.selectAll(); - textField.requestFocus(); - }); - } - - @Override - public void cancelEdit() { - // TODO Auto-generated method stub - } - - @Override - public void updateItem(Integer item, boolean empty) { - Platform.runLater(()->{ - textField.selectAll(); - textField.requestFocus(); - }); - } - - @Override - public Region createNode(Integer value, DoubleBinding minWidthBinding, EventHandler keyEventsHandler, ChangeListener focusChangeListener) { - StackPane pane = new StackPane(); - pane.setStyle("-fx-padding:-10 0 -10 0"); - textField = new JFXTextField(value+""); - textField.minWidthProperty().bind(minWidthBinding); - textField.setOnKeyPressed(keyEventsHandler); - textField.focusedProperty().addListener(focusChangeListener); - NumberValidator validator = new NumberValidator(); - validator.setMessage("Value must be a number"); - textField.getValidators().add(validator); - pane.getChildren().add(textField); - return pane; - } - - @Override - public void setValue(Integer value) { - textField.setText(value+""); - } - - @Override - public Integer getValue() { - return Integer.parseInt(textField.getText()); - } - - @Override - public void validateValue() throws Exception { - if(!textField.validate()) throw new Exception(); - } -} +/* + * 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.cells.editors; + +import com.jfoenix.controls.JFXTextField; +import com.jfoenix.controls.cells.editors.base.EditorNodeBuilder; +import com.jfoenix.validation.NumberValidator; +import javafx.application.Platform; +import javafx.beans.binding.DoubleBinding; +import javafx.beans.value.ChangeListener; +import javafx.event.EventHandler; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; + +/** + *

Text field cell editor (numbers only)

+ * this an example of the cell editor, it creates a JFXTextField node to + * allow the user to edit the cell value + *

+ * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class IntegerTextFieldEditorBuilder implements EditorNodeBuilder { + + private JFXTextField textField; + + @Override + public void startEdit() { + Platform.runLater(()->{ + textField.selectAll(); + textField.requestFocus(); + }); + } + + @Override + public void cancelEdit() { + // TODO Auto-generated method stub + } + + @Override + public void updateItem(Integer item, boolean empty) { + Platform.runLater(()->{ + textField.selectAll(); + textField.requestFocus(); + }); + } + + @Override + public Region createNode(Integer value, DoubleBinding minWidthBinding, EventHandler keyEventsHandler, ChangeListener focusChangeListener) { + StackPane pane = new StackPane(); + pane.setStyle("-fx-padding:-10 0 -10 0"); + textField = new JFXTextField(value+""); + textField.minWidthProperty().bind(minWidthBinding); + textField.setOnKeyPressed(keyEventsHandler); + textField.focusedProperty().addListener(focusChangeListener); + NumberValidator validator = new NumberValidator(); + validator.setMessage("Value must be a number"); + textField.getValidators().add(validator); + pane.getChildren().add(textField); + return pane; + } + + @Override + public void setValue(Integer value) { + textField.setText(value+""); + } + + @Override + public Integer getValue() { + return Integer.parseInt(textField.getText()); + } + + @Override + public void validateValue() throws Exception { + if(!textField.validate()) throw new Exception(); + } +} diff --git a/src/com/jfoenix/controls/cells/editors/TextFieldEditorBuilder.java b/jfoenix/src/main/java/com/jfoenix/controls/cells/editors/TextFieldEditorBuilder.java similarity index 96% rename from src/com/jfoenix/controls/cells/editors/TextFieldEditorBuilder.java rename to jfoenix/src/main/java/com/jfoenix/controls/cells/editors/TextFieldEditorBuilder.java index e5f773f0..ae942644 100644 --- a/src/com/jfoenix/controls/cells/editors/TextFieldEditorBuilder.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/cells/editors/TextFieldEditorBuilder.java @@ -1,93 +1,93 @@ -/* - * 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.cells.editors; - -import com.jfoenix.controls.JFXTextField; -import com.jfoenix.controls.cells.editors.base.EditorNodeBuilder; -import javafx.application.Platform; -import javafx.beans.binding.DoubleBinding; -import javafx.beans.value.ChangeListener; -import javafx.event.EventHandler; -import javafx.scene.input.KeyEvent; -import javafx.scene.layout.Region; -import javafx.scene.layout.StackPane; - -/** - *

Text field cell editor

- * this an example of the cell editor, it creates a JFXTextField node to - * allow the user to edit the cell value - *

- * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class TextFieldEditorBuilder implements EditorNodeBuilder { - - private JFXTextField textField; - - @Override - public void startEdit() { - Platform.runLater(()->{ - textField.selectAll(); - textField.requestFocus(); - }); - } - - @Override - public void cancelEdit() { - // TODO Auto-generated method stub - } - - @Override - public void updateItem(String item, boolean empty) { - Platform.runLater(()->{ - textField.selectAll(); - textField.requestFocus(); - }); - } - - @Override - public Region createNode(String value, DoubleBinding minWidthBinding, EventHandler keyEventsHandler, ChangeListener focusChangeListener) { - StackPane pane = new StackPane(); - pane.setStyle("-fx-padding:-10 0 -10 0"); - textField = new JFXTextField(value); - textField.setStyle("-fx-background-color:TRANSPARENT;"); - textField.minWidthProperty().bind(minWidthBinding); - textField.setOnKeyPressed(keyEventsHandler); - textField.focusedProperty().addListener(focusChangeListener); - pane.getChildren().add(textField); - return pane; - } - - @Override - public void setValue(String value) { - textField.setText(value); - } - - @Override - public String getValue() { - return textField.getText(); - } - - @Override - public void validateValue() throws Exception { - - } -} +/* + * 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.cells.editors; + +import com.jfoenix.controls.JFXTextField; +import com.jfoenix.controls.cells.editors.base.EditorNodeBuilder; +import javafx.application.Platform; +import javafx.beans.binding.DoubleBinding; +import javafx.beans.value.ChangeListener; +import javafx.event.EventHandler; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; + +/** + *

Text field cell editor

+ * this an example of the cell editor, it creates a JFXTextField node to + * allow the user to edit the cell value + *

+ * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class TextFieldEditorBuilder implements EditorNodeBuilder { + + private JFXTextField textField; + + @Override + public void startEdit() { + Platform.runLater(()->{ + textField.selectAll(); + textField.requestFocus(); + }); + } + + @Override + public void cancelEdit() { + // TODO Auto-generated method stub + } + + @Override + public void updateItem(String item, boolean empty) { + Platform.runLater(()->{ + textField.selectAll(); + textField.requestFocus(); + }); + } + + @Override + public Region createNode(String value, DoubleBinding minWidthBinding, EventHandler keyEventsHandler, ChangeListener focusChangeListener) { + StackPane pane = new StackPane(); + pane.setStyle("-fx-padding:-10 0 -10 0"); + textField = new JFXTextField(value); + textField.setStyle("-fx-background-color:TRANSPARENT;"); + textField.minWidthProperty().bind(minWidthBinding); + textField.setOnKeyPressed(keyEventsHandler); + textField.focusedProperty().addListener(focusChangeListener); + pane.getChildren().add(textField); + return pane; + } + + @Override + public void setValue(String value) { + textField.setText(value); + } + + @Override + public String getValue() { + return textField.getText(); + } + + @Override + public void validateValue() throws Exception { + + } +} diff --git a/src/com/jfoenix/controls/cells/editors/base/EditorNodeBuilder.java b/jfoenix/src/main/java/com/jfoenix/controls/cells/editors/base/EditorNodeBuilder.java similarity index 97% rename from src/com/jfoenix/controls/cells/editors/base/EditorNodeBuilder.java rename to jfoenix/src/main/java/com/jfoenix/controls/cells/editors/base/EditorNodeBuilder.java index 4ef308d6..c5862578 100644 --- a/src/com/jfoenix/controls/cells/editors/base/EditorNodeBuilder.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/cells/editors/base/EditorNodeBuilder.java @@ -1,87 +1,87 @@ -/* - * 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.cells.editors.base; - -import javafx.beans.binding.DoubleBinding; -import javafx.beans.value.ChangeListener; -import javafx.event.EventHandler; -import javafx.scene.input.KeyEvent; -import javafx.scene.layout.Region; - -/** - *

Editor Builder

- * this a builder interface to create editors for treetableview/tableview cells - *

- * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public interface EditorNodeBuilder { - /** - * This method is called when the editor start editing the cell - * @return Nothing - */ - public void startEdit(); - /** - * This method is called when the editor cancel editing the cell - * @return Nothing - */ - public void cancelEdit(); - /** - * This method is called when the editor updates the visuals of the cell - * - * @param the new item for the cell - * @param whether or not this cell holds a value - * @return Nothing - */ - public void updateItem(T item, boolean empty); - /** - * This method is will create the editor node to be displayed when - * editing the cell - * - * @param value current value of the cell - * @param minWidthBinding {@link javafx.beans.binding.DoubleBinding DoubleBinding} to bind the minimum width of the editor node - * @param keyEventsHandler keyboard events handler for the cell - * @param focusChangeListener focus change listener for the cell - * @return the editor node - */ - public Region createNode(T value, DoubleBinding minWidthBinding , EventHandler keyEventsHandler, ChangeListener focusChangeListener); - /** - * This method is used to update the editor node to corresponde with - * the new value of the cell - * - * @param value the new value of the cell - * @return Nothing - */ - public void setValue(T value); - /** - * This method is used to get the current value from the editor node - * - * @return T the value of the editor node - */ - public T getValue(); - /** - * This method will be called before committing the new value of the cell - * - * @throws Exception - * @return Nothing - */ - public void validateValue() throws Exception; -} +/* + * 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.cells.editors.base; + +import javafx.beans.binding.DoubleBinding; +import javafx.beans.value.ChangeListener; +import javafx.event.EventHandler; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.Region; + +/** + *

Editor Builder

+ * this a builder interface to create editors for treetableview/tableview cells + *

+ * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public interface EditorNodeBuilder { + /** + * This method is called when the editor start editing the cell + * @return Nothing + */ + public void startEdit(); + /** + * This method is called when the editor cancel editing the cell + * @return Nothing + */ + public void cancelEdit(); + /** + * This method is called when the editor updates the visuals of the cell + * + * @param the new item for the cell + * @param whether or not this cell holds a value + * @return Nothing + */ + public void updateItem(T item, boolean empty); + /** + * This method is will create the editor node to be displayed when + * editing the cell + * + * @param value current value of the cell + * @param minWidthBinding {@link javafx.beans.binding.DoubleBinding DoubleBinding} to bind the minimum width of the editor node + * @param keyEventsHandler keyboard events handler for the cell + * @param focusChangeListener focus change listener for the cell + * @return the editor node + */ + public Region createNode(T value, DoubleBinding minWidthBinding , EventHandler keyEventsHandler, ChangeListener focusChangeListener); + /** + * This method is used to update the editor node to corresponde with + * the new value of the cell + * + * @param value the new value of the cell + * @return Nothing + */ + public void setValue(T value); + /** + * This method is used to get the current value from the editor node + * + * @return T the value of the editor node + */ + public T getValue(); + /** + * This method will be called before committing the new value of the cell + * + * @throws Exception + * @return Nothing + */ + public void validateValue() throws Exception; +} diff --git a/src/com/jfoenix/controls/cells/editors/base/GenericEditableTreeTableCell.java b/jfoenix/src/main/java/com/jfoenix/controls/cells/editors/base/GenericEditableTreeTableCell.java similarity index 97% rename from src/com/jfoenix/controls/cells/editors/base/GenericEditableTreeTableCell.java rename to jfoenix/src/main/java/com/jfoenix/controls/cells/editors/base/GenericEditableTreeTableCell.java index f9ecc84d..727a6a0f 100644 --- a/src/com/jfoenix/controls/cells/editors/base/GenericEditableTreeTableCell.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/cells/editors/base/GenericEditableTreeTableCell.java @@ -1,264 +1,264 @@ -/* - * 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.cells.editors.base; - -import com.jfoenix.controls.JFXTreeTableColumn; -import com.jfoenix.controls.cells.editors.TextFieldEditorBuilder; -import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject; -import javafx.beans.binding.Bindings; -import javafx.beans.binding.DoubleBinding; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; -import javafx.event.EventHandler; -import javafx.scene.Node; -import javafx.scene.control.ContentDisplay; -import javafx.scene.control.TreeTableColumn; -import javafx.scene.input.KeyCode; -import javafx.scene.input.KeyEvent; -import javafx.scene.layout.Region; - -import java.util.ArrayList; -import java.util.List; -/** - *

Generic Editable Tree Table cell

- * Provides the base for an editable table cell using a text field. Sub-classes can provide formatters for display and a - * commitHelper to control when editing is committed. - *

- * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class GenericEditableTreeTableCell extends JFXTreeTableCell { - protected EditorNodeBuilder builder; - protected Region editorNode; - - - /** - * constructor that takes a custom builder to edit the cell - * @param builder - */ - public GenericEditableTreeTableCell(EditorNodeBuilder builder) { - this.builder = builder; - } - - /** - * constructor that creates the default {@link com.jfoenix.controls.cells.editors.TextFieldEditorBuilder TextField} editor node - * to edit the cell - */ - public GenericEditableTreeTableCell() { - builder = new TextFieldEditorBuilder(); - } - /** - * Any action attempting to commit an edit should call this method rather than commit the edit directly itself. This - * method will perform any validation and conversion required on the value. For text values that normally means this - * method just commits the edit but for numeric values, for example, it may first parse the given input.

The only - * situation that needs to be treated specially is when the field is losing focus. If you user hits enter to commit the - * cell with bad data we can happily cancel the commit and force them to enter a real value. If they click away from the - * cell though we want to give them their old value back. - * - * @param losingFocus true if the reason for the call was because the field is losing focus. - */ - protected void commitHelper( boolean losingFocus ) { - if( editorNode == null ) return; - try { - builder.validateValue(); - commitEdit(((T) builder.getValue())); - } catch (Exception ex) { - //Most of the time we don't mind if there is a parse exception as it - //indicates duff user data but in the case where we are losing focus - //it means the user has clicked away with bad data in the cell. In that - //situation we want to just cancel the editing and show them the old - //value. - if( losingFocus ) { - cancelEdit(); - } - } - - } - /** - * Provides the string representation of the value of this cell when the cell is not being edited. - */ - protected Object getValue(){ - return getItem() == null ? "" : getItem(); - } - - @Override - public void startEdit() { - if(checkGroupedColumn()){ - super.startEdit(); - if (editorNode == null) { - createEditorNode(); - } - builder.startEdit(); - setGraphic(editorNode); - setContentDisplay(ContentDisplay.GRAPHIC_ONLY); - } - } - - @Override - public void cancelEdit() { - super.cancelEdit(); - builder.cancelEdit(); - builder.setValue(getValue()); - setContentDisplay(ContentDisplay.TEXT_ONLY); - //Once the edit has been cancelled we no longer need the editor - //so we mark it for cleanup here. Note though that you have to handle - //this situation in the focus listener which gets fired at the end - //of the editing. - editorNode = null; - } - - /** - * only allows editing for items that are not grouped - * @return whether the item is grouped or not - */ - private boolean checkGroupedColumn(){ - boolean allowEdit = true; - if(getTreeTableRow().getTreeItem()!=null){ - Object rowObject = getTreeTableRow().getTreeItem().getValue(); - if(rowObject instanceof RecursiveTreeObject && rowObject.getClass() == RecursiveTreeObject.class){ - allowEdit = false; - }else{ - // check grouped columns in the tableview - if(getTableColumn() instanceof JFXTreeTableColumn && ((JFXTreeTableColumn)getTableColumn()).isGrouped()){ - // make sure that the object is a direct child to a group node - if(getTreeTableRow().getTreeItem().getParent() != null && - getTreeTableRow().getTreeItem().getParent().getValue().getClass() == RecursiveTreeObject.class) - allowEdit = false; - } - } - } - return allowEdit; - } - - @Override - public void updateItem(T item, boolean empty) { - super.updateItem(item, empty); - if (empty) { - setText(null); - setGraphic(null); - } else { - if (isEditing() && checkGroupedColumn()) { - - if (editorNode != null) { - builder.setValue(getValue()); - } - setGraphic(editorNode); - setContentDisplay(ContentDisplay.GRAPHIC_ONLY); - builder.updateItem(item, empty); - } else { - Object value = getValue(); - if(value instanceof Node) { - setGraphic((Node) value); - setContentDisplay(ContentDisplay.GRAPHIC_ONLY); - } else { - setText(value.toString()); - setContentDisplay(ContentDisplay.TEXT_ONLY); - } - } - } - } - - private void createEditorNode() { - - EventHandler keyEventsHandler = new EventHandler() { - @Override - public void handle(KeyEvent t) { - if (t.getCode() == KeyCode.ENTER) { - commitHelper(false); - } else if (t.getCode() == KeyCode.ESCAPE) { - cancelEdit(); - } else if (t.getCode() == KeyCode.TAB) { - commitHelper(false); - - TreeTableColumn nextColumn = getNextColumn(!t.isShiftDown()); - if (nextColumn != null) { - getTreeTableView().edit(getIndex(), nextColumn); - } - } - } - }; - - ChangeListener focusChangeListener = new ChangeListener() { - @Override - public void changed(ObservableValue observable, Boolean oldValue, Boolean newValue) { - //This focus listener fires at the end of cell editing when focus is lost - //and when enter is pressed (because that causes the text field to lose focus). - //The problem is that if enter is pressed then cancelEdit is called before this - //listener runs and therefore the text field has been cleaned up. If the - //text field is null we don't commit the edit. This has the useful side effect - //of stopping the double commit. - if (!newValue && editorNode != null) { - commitHelper(true); - } - } - }; - DoubleBinding minWidthBinding = Bindings.createDoubleBinding(()->{ - return this.getWidth() - this.getGraphicTextGap()*2 - this.getBaselineOffset() ; - }, this.widthProperty(), this.graphicTextGapProperty()); - editorNode = builder.createNode(getValue(), minWidthBinding, keyEventsHandler, focusChangeListener); - } - - /** - * @param forward true gets the column to the right, false the column to the left of the current column - * @return - */ - private TreeTableColumn getNextColumn(boolean forward) { - List> columns = new ArrayList<>(); - for (TreeTableColumn column : getTreeTableView().getColumns()) { - columns.addAll(getLeaves(column)); - } - //There is no other column that supports editing. - if (columns.size() < 2) { - return null; - } - int currentIndex = columns.indexOf(getTableColumn()); - int nextIndex = currentIndex; - if (forward) { - nextIndex++; - if (nextIndex > columns.size() - 1) { - nextIndex = 0; - } - } else { - nextIndex--; - if (nextIndex < 0) { - nextIndex = columns.size() - 1; - } - } - return columns.get(nextIndex); - } - - - private List> getLeaves(TreeTableColumn root) { - List> columns = new ArrayList<>(); - if (root.getColumns().isEmpty()) { - //We only want the leaves that are editable. - if (root.isEditable()) { - columns.add(root); - } - return columns; - } else { - for (TreeTableColumn column : root.getColumns()) { - columns.addAll(getLeaves(column)); - } - return columns; - } - } +/* + * 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.cells.editors.base; + +import com.jfoenix.controls.JFXTreeTableColumn; +import com.jfoenix.controls.cells.editors.TextFieldEditorBuilder; +import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject; +import javafx.beans.binding.Bindings; +import javafx.beans.binding.DoubleBinding; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.event.EventHandler; +import javafx.scene.Node; +import javafx.scene.control.ContentDisplay; +import javafx.scene.control.TreeTableColumn; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.Region; + +import java.util.ArrayList; +import java.util.List; +/** + *

Generic Editable Tree Table cell

+ * Provides the base for an editable table cell using a text field. Sub-classes can provide formatters for display and a + * commitHelper to control when editing is committed. + *

+ * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class GenericEditableTreeTableCell extends JFXTreeTableCell { + protected EditorNodeBuilder builder; + protected Region editorNode; + + + /** + * constructor that takes a custom builder to edit the cell + * @param builder + */ + public GenericEditableTreeTableCell(EditorNodeBuilder builder) { + this.builder = builder; + } + + /** + * constructor that creates the default {@link com.jfoenix.controls.cells.editors.TextFieldEditorBuilder TextField} editor node + * to edit the cell + */ + public GenericEditableTreeTableCell() { + builder = new TextFieldEditorBuilder(); + } + /** + * Any action attempting to commit an edit should call this method rather than commit the edit directly itself. This + * method will perform any validation and conversion required on the value. For text values that normally means this + * method just commits the edit but for numeric values, for example, it may first parse the given input.

The only + * situation that needs to be treated specially is when the field is losing focus. If you user hits enter to commit the + * cell with bad data we can happily cancel the commit and force them to enter a real value. If they click away from the + * cell though we want to give them their old value back. + * + * @param losingFocus true if the reason for the call was because the field is losing focus. + */ + protected void commitHelper( boolean losingFocus ) { + if( editorNode == null ) return; + try { + builder.validateValue(); + commitEdit(((T) builder.getValue())); + } catch (Exception ex) { + //Most of the time we don't mind if there is a parse exception as it + //indicates duff user data but in the case where we are losing focus + //it means the user has clicked away with bad data in the cell. In that + //situation we want to just cancel the editing and show them the old + //value. + if( losingFocus ) { + cancelEdit(); + } + } + + } + /** + * Provides the string representation of the value of this cell when the cell is not being edited. + */ + protected Object getValue(){ + return getItem() == null ? "" : getItem(); + } + + @Override + public void startEdit() { + if(checkGroupedColumn()){ + super.startEdit(); + if (editorNode == null) { + createEditorNode(); + } + builder.startEdit(); + setGraphic(editorNode); + setContentDisplay(ContentDisplay.GRAPHIC_ONLY); + } + } + + @Override + public void cancelEdit() { + super.cancelEdit(); + builder.cancelEdit(); + builder.setValue(getValue()); + setContentDisplay(ContentDisplay.TEXT_ONLY); + //Once the edit has been cancelled we no longer need the editor + //so we mark it for cleanup here. Note though that you have to handle + //this situation in the focus listener which gets fired at the end + //of the editing. + editorNode = null; + } + + /** + * only allows editing for items that are not grouped + * @return whether the item is grouped or not + */ + private boolean checkGroupedColumn(){ + boolean allowEdit = true; + if(getTreeTableRow().getTreeItem()!=null){ + Object rowObject = getTreeTableRow().getTreeItem().getValue(); + if(rowObject instanceof RecursiveTreeObject && rowObject.getClass() == RecursiveTreeObject.class){ + allowEdit = false; + }else{ + // check grouped columns in the tableview + if(getTableColumn() instanceof JFXTreeTableColumn && ((JFXTreeTableColumn)getTableColumn()).isGrouped()){ + // make sure that the object is a direct child to a group node + if(getTreeTableRow().getTreeItem().getParent() != null && + getTreeTableRow().getTreeItem().getParent().getValue().getClass() == RecursiveTreeObject.class) + allowEdit = false; + } + } + } + return allowEdit; + } + + @Override + public void updateItem(T item, boolean empty) { + super.updateItem(item, empty); + if (empty) { + setText(null); + setGraphic(null); + } else { + if (isEditing() && checkGroupedColumn()) { + + if (editorNode != null) { + builder.setValue(getValue()); + } + setGraphic(editorNode); + setContentDisplay(ContentDisplay.GRAPHIC_ONLY); + builder.updateItem(item, empty); + } else { + Object value = getValue(); + if(value instanceof Node) { + setGraphic((Node) value); + setContentDisplay(ContentDisplay.GRAPHIC_ONLY); + } else { + setText(value.toString()); + setContentDisplay(ContentDisplay.TEXT_ONLY); + } + } + } + } + + private void createEditorNode() { + + EventHandler keyEventsHandler = new EventHandler() { + @Override + public void handle(KeyEvent t) { + if (t.getCode() == KeyCode.ENTER) { + commitHelper(false); + } else if (t.getCode() == KeyCode.ESCAPE) { + cancelEdit(); + } else if (t.getCode() == KeyCode.TAB) { + commitHelper(false); + + TreeTableColumn nextColumn = getNextColumn(!t.isShiftDown()); + if (nextColumn != null) { + getTreeTableView().edit(getIndex(), nextColumn); + } + } + } + }; + + ChangeListener focusChangeListener = new ChangeListener() { + @Override + public void changed(ObservableValue observable, Boolean oldValue, Boolean newValue) { + //This focus listener fires at the end of cell editing when focus is lost + //and when enter is pressed (because that causes the text field to lose focus). + //The problem is that if enter is pressed then cancelEdit is called before this + //listener runs and therefore the text field has been cleaned up. If the + //text field is null we don't commit the edit. This has the useful side effect + //of stopping the double commit. + if (!newValue && editorNode != null) { + commitHelper(true); + } + } + }; + DoubleBinding minWidthBinding = Bindings.createDoubleBinding(()->{ + return this.getWidth() - this.getGraphicTextGap()*2 - this.getBaselineOffset() ; + }, this.widthProperty(), this.graphicTextGapProperty()); + editorNode = builder.createNode(getValue(), minWidthBinding, keyEventsHandler, focusChangeListener); + } + + /** + * @param forward true gets the column to the right, false the column to the left of the current column + * @return + */ + private TreeTableColumn getNextColumn(boolean forward) { + List> columns = new ArrayList<>(); + for (TreeTableColumn column : getTreeTableView().getColumns()) { + columns.addAll(getLeaves(column)); + } + //There is no other column that supports editing. + if (columns.size() < 2) { + return null; + } + int currentIndex = columns.indexOf(getTableColumn()); + int nextIndex = currentIndex; + if (forward) { + nextIndex++; + if (nextIndex > columns.size() - 1) { + nextIndex = 0; + } + } else { + nextIndex--; + if (nextIndex < 0) { + nextIndex = columns.size() - 1; + } + } + return columns.get(nextIndex); + } + + + private List> getLeaves(TreeTableColumn root) { + List> columns = new ArrayList<>(); + if (root.getColumns().isEmpty()) { + //We only want the leaves that are editable. + if (root.isEditable()) { + columns.add(root); + } + return columns; + } else { + for (TreeTableColumn column : root.getColumns()) { + columns.addAll(getLeaves(column)); + } + return columns; + } + } } \ No newline at end of file diff --git a/src/com/jfoenix/controls/cells/editors/base/JFXTreeTableCell.java b/jfoenix/src/main/java/com/jfoenix/controls/cells/editors/base/JFXTreeTableCell.java similarity index 97% rename from src/com/jfoenix/controls/cells/editors/base/JFXTreeTableCell.java rename to jfoenix/src/main/java/com/jfoenix/controls/cells/editors/base/JFXTreeTableCell.java index 2e1ce61e..dbfb7ada 100644 --- a/src/com/jfoenix/controls/cells/editors/base/JFXTreeTableCell.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/cells/editors/base/JFXTreeTableCell.java @@ -1,37 +1,37 @@ -/* - * 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.cells.editors.base; - -import com.jfoenix.skins.JFXTreeTableCellSkin; -import javafx.scene.control.Skin; -import javafx.scene.control.TreeTableCell; - -/** - * overrides the cell skin to be able to use the {@link com.jfoenix.controls.JFXTreeTableView JFXTreeTableView} - * features such as grouping - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXTreeTableCell extends TreeTableCell { - @Override protected Skin createDefaultSkin() { - return new JFXTreeTableCellSkin(this); - } -} +/* + * 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.cells.editors.base; + +import com.jfoenix.skins.JFXTreeTableCellSkin; +import javafx.scene.control.Skin; +import javafx.scene.control.TreeTableCell; + +/** + * overrides the cell skin to be able to use the {@link com.jfoenix.controls.JFXTreeTableView JFXTreeTableView} + * features such as grouping + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXTreeTableCell extends TreeTableCell { + @Override protected Skin createDefaultSkin() { + return new JFXTreeTableCellSkin(this); + } +} diff --git a/src/com/jfoenix/controls/datamodels/treetable/RecursiveTreeObject.java b/jfoenix/src/main/java/com/jfoenix/controls/datamodels/treetable/RecursiveTreeObject.java similarity index 96% rename from src/com/jfoenix/controls/datamodels/treetable/RecursiveTreeObject.java rename to jfoenix/src/main/java/com/jfoenix/controls/datamodels/treetable/RecursiveTreeObject.java index 41f37b6a..c95de5cf 100644 --- a/src/com/jfoenix/controls/datamodels/treetable/RecursiveTreeObject.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/datamodels/treetable/RecursiveTreeObject.java @@ -1,89 +1,89 @@ -/* - * 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.datamodels.treetable; - -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.scene.control.TreeTableColumn; - -/** - * data model that is used in JFXTreeTableView, it's used to implement - * the grouping feature. - *

- * Note: the data object used in JFXTreeTableView must extends this class - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - * - * @param is the concrete object of the Tree table - */ -public class RecursiveTreeObject { - - /** - * gropued children objects - */ - ObservableList children = FXCollections.observableArrayList(); - - public ObservableList getChildren(){ - return children; - } - - public void setChildren(ObservableList children) { - this.children = children; - } - - /** - * Whether or not the object is grouped by a specified tree table column - */ - ObjectProperty> groupedColumn = new SimpleObjectProperty<>(); - - public final ObjectProperty> groupedColumnProperty() { - return this.groupedColumn; - } - - public final TreeTableColumn getGroupedColumn() { - return this.groupedColumnProperty().get(); - } - - public final void setGroupedColumn(final TreeTableColumn groupedColumn) { - this.groupedColumnProperty().set(groupedColumn); - } - - /** - * the value that must be shown when grouped - */ - ObjectProperty groupedValue = new SimpleObjectProperty<>(); - - public final ObjectProperty groupedValueProperty() { - return this.groupedValue; - } - - public final java.lang.Object getGroupedValue() { - return this.groupedValueProperty().get(); - } - - public final void setGroupedValue(final java.lang.Object groupedValue) { - this.groupedValueProperty().set(groupedValue); - } - - -} +/* + * 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.datamodels.treetable; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.scene.control.TreeTableColumn; + +/** + * data model that is used in JFXTreeTableView, it's used to implement + * the grouping feature. + *

+ * Note: the data object used in JFXTreeTableView must extends this class + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + * + * @param is the concrete object of the Tree table + */ +public class RecursiveTreeObject { + + /** + * gropued children objects + */ + ObservableList children = FXCollections.observableArrayList(); + + public ObservableList getChildren(){ + return children; + } + + public void setChildren(ObservableList children) { + this.children = children; + } + + /** + * Whether or not the object is grouped by a specified tree table column + */ + ObjectProperty> groupedColumn = new SimpleObjectProperty<>(); + + public final ObjectProperty> groupedColumnProperty() { + return this.groupedColumn; + } + + public final TreeTableColumn getGroupedColumn() { + return this.groupedColumnProperty().get(); + } + + public final void setGroupedColumn(final TreeTableColumn groupedColumn) { + this.groupedColumnProperty().set(groupedColumn); + } + + /** + * the value that must be shown when grouped + */ + ObjectProperty groupedValue = new SimpleObjectProperty<>(); + + public final ObjectProperty groupedValueProperty() { + return this.groupedValue; + } + + public final java.lang.Object getGroupedValue() { + return this.groupedValueProperty().get(); + } + + public final void setGroupedValue(final java.lang.Object groupedValue) { + this.groupedValueProperty().set(groupedValue); + } + + +} diff --git a/src/com/jfoenix/controls/events/JFXDialogEvent.java b/jfoenix/src/main/java/com/jfoenix/controls/events/JFXDialogEvent.java similarity index 96% rename from src/com/jfoenix/controls/events/JFXDialogEvent.java rename to jfoenix/src/main/java/com/jfoenix/controls/events/JFXDialogEvent.java index 541649fb..ae931c71 100644 --- a/src/com/jfoenix/controls/events/JFXDialogEvent.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/events/JFXDialogEvent.java @@ -1,63 +1,63 @@ -/* - * 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.events; - -import com.jfoenix.controls.JFXDialog; -import javafx.event.Event; -import javafx.event.EventType; - -/** - * JFXDialog events, used exclusively by the following methods: - *
    - *
  • {@link JFXDialog#close()} - *
  • {@link JFXDialog#getShowAnimation()} - *
- * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXDialogEvent extends Event{ - - private static final long serialVersionUID = 1L; - - /** - * Construct a new JFXDialog {@code Event} with the specified event type - * @param eventType the event type - */ - public JFXDialogEvent(EventType eventType) { - super(eventType); - } - - /** - * This event occurs when a JFXDialog is closed, no longer visible to the user - * ( after the exit animation ends ) - */ - public static final EventType CLOSED = - new EventType (Event.ANY, "DIALOG_CLOSED"); - - /** - * This event occurs when a JFXDialog is opened, visible to the user - * ( after the entrance animation ends ) - */ - public static final EventType OPENED = - new EventType (Event.ANY, "DIALOG_OPENED"); - - -} +/* + * 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.events; + +import com.jfoenix.controls.JFXDialog; +import javafx.event.Event; +import javafx.event.EventType; + +/** + * JFXDialog events, used exclusively by the following methods: + *
    + *
  • {@link JFXDialog#close()} + *
  • {@link JFXDialog#getShowAnimation()} + *
+ * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXDialogEvent extends Event{ + + private static final long serialVersionUID = 1L; + + /** + * Construct a new JFXDialog {@code Event} with the specified event type + * @param eventType the event type + */ + public JFXDialogEvent(EventType eventType) { + super(eventType); + } + + /** + * This event occurs when a JFXDialog is closed, no longer visible to the user + * ( after the exit animation ends ) + */ + public static final EventType CLOSED = + new EventType (Event.ANY, "DIALOG_CLOSED"); + + /** + * This event occurs when a JFXDialog is opened, visible to the user + * ( after the entrance animation ends ) + */ + public static final EventType OPENED = + new EventType (Event.ANY, "DIALOG_OPENED"); + + +} diff --git a/src/com/jfoenix/controls/events/JFXDrawerEvent.java b/jfoenix/src/main/java/com/jfoenix/controls/events/JFXDrawerEvent.java similarity index 96% rename from src/com/jfoenix/controls/events/JFXDrawerEvent.java rename to jfoenix/src/main/java/com/jfoenix/controls/events/JFXDrawerEvent.java index 31ba0af5..e00e2fe4 100644 --- a/src/com/jfoenix/controls/events/JFXDrawerEvent.java +++ b/jfoenix/src/main/java/com/jfoenix/controls/events/JFXDrawerEvent.java @@ -1,79 +1,79 @@ -/* - * 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.events; - -import com.jfoenix.controls.JFXDrawer; -import javafx.event.Event; -import javafx.event.EventType; - -/** - * JFXDrawer events, used exclusively by the following methods: - *
    - *
  • {@link JFXDrawer#open()} - *
  • {@link JFXDrawer#close()} - *
- * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXDrawerEvent extends Event { - - private static final long serialVersionUID = 1L; - - /** - * Construct a new JFXDrawer {@code Event} with the specified event type - * @param eventType the event type - */ - public JFXDrawerEvent(EventType eventType) { - super(eventType); - } - - /** - * This event occurs when a JFXDrawer is closed, no longer visible to the user - * ( after the exit animation ends ) - */ - public static final EventType CLOSED = - new EventType (Event.ANY, "DRAWER_CLOSED"); - - /** - * This event occurs when a JFXDrawer is drawn, visible to the user - * ( after the entrance animation ends ) - */ - public static final EventType OPENED = - new EventType (Event.ANY, "DRAWER_OPENED"); - - /** - * This event occurs when a JFXDrawer is being drawn, visible to the user - * ( after the entrance animation ends ) - */ - public static final EventType OPENING = - new EventType (Event.ANY, "DRAWER_OPENING"); - - - /** - * This event occurs when a JFXDrawer is being closed, will become invisible to the user - * at the end of the animation - * ( after the entrance animation ends ) - */ - public static final EventType CLOSING = - new EventType (Event.ANY, "DRAWER_CLOSING"); - - -} +/* + * 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.events; + +import com.jfoenix.controls.JFXDrawer; +import javafx.event.Event; +import javafx.event.EventType; + +/** + * JFXDrawer events, used exclusively by the following methods: + *
    + *
  • {@link JFXDrawer#open()} + *
  • {@link JFXDrawer#close()} + *
+ * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXDrawerEvent extends Event { + + private static final long serialVersionUID = 1L; + + /** + * Construct a new JFXDrawer {@code Event} with the specified event type + * @param eventType the event type + */ + public JFXDrawerEvent(EventType eventType) { + super(eventType); + } + + /** + * This event occurs when a JFXDrawer is closed, no longer visible to the user + * ( after the exit animation ends ) + */ + public static final EventType CLOSED = + new EventType (Event.ANY, "DRAWER_CLOSED"); + + /** + * This event occurs when a JFXDrawer is drawn, visible to the user + * ( after the entrance animation ends ) + */ + public static final EventType OPENED = + new EventType (Event.ANY, "DRAWER_OPENED"); + + /** + * This event occurs when a JFXDrawer is being drawn, visible to the user + * ( after the entrance animation ends ) + */ + public static final EventType OPENING = + new EventType (Event.ANY, "DRAWER_OPENING"); + + + /** + * This event occurs when a JFXDrawer is being closed, will become invisible to the user + * at the end of the animation + * ( after the entrance animation ends ) + */ + public static final EventType CLOSING = + new EventType (Event.ANY, "DRAWER_CLOSING"); + + +} diff --git a/src/com/jfoenix/converters/ButtonTypeConverter.java b/jfoenix/src/main/java/com/jfoenix/converters/ButtonTypeConverter.java similarity index 97% rename from src/com/jfoenix/converters/ButtonTypeConverter.java rename to jfoenix/src/main/java/com/jfoenix/converters/ButtonTypeConverter.java index 7e7156f4..d9cf1032 100644 --- a/src/com/jfoenix/converters/ButtonTypeConverter.java +++ b/jfoenix/src/main/java/com/jfoenix/converters/ButtonTypeConverter.java @@ -1,61 +1,61 @@ -/* - * 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.converters; - -import com.jfoenix.controls.JFXButton.ButtonType; -import com.sun.javafx.css.StyleConverterImpl; -import javafx.css.ParsedValue; -import javafx.css.StyleConverter; -import javafx.scene.text.Font; - -/** - * Converts the CSS for -fx-button-type items into ButtonType. - * it's used in JFXButton - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class ButtonTypeConverter extends StyleConverterImpl { - // lazy, thread-safe instatiation - private static class Holder { - static final ButtonTypeConverter INSTANCE = new ButtonTypeConverter(); - } - public static StyleConverter getInstance() { - return Holder.INSTANCE; - } - private ButtonTypeConverter() { - super(); - } - - @Override - public ButtonType convert(ParsedValue value, Font not_used) { - String string = value.getValue(); - try { - return ButtonType.valueOf(string); - } catch (IllegalArgumentException | NullPointerException exception) { - return ButtonType.FLAT; - } - } - - @Override - public String toString() { - return "ButtonTypeConverter"; - } +/* + * 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.converters; + +import com.jfoenix.controls.JFXButton.ButtonType; +import com.sun.javafx.css.StyleConverterImpl; +import javafx.css.ParsedValue; +import javafx.css.StyleConverter; +import javafx.scene.text.Font; + +/** + * Converts the CSS for -fx-button-type items into ButtonType. + * it's used in JFXButton + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class ButtonTypeConverter extends StyleConverterImpl { + // lazy, thread-safe instatiation + private static class Holder { + static final ButtonTypeConverter INSTANCE = new ButtonTypeConverter(); + } + public static StyleConverter getInstance() { + return Holder.INSTANCE; + } + private ButtonTypeConverter() { + super(); + } + + @Override + public ButtonType convert(ParsedValue value, Font not_used) { + String string = value.getValue(); + try { + return ButtonType.valueOf(string); + } catch (IllegalArgumentException | NullPointerException exception) { + return ButtonType.FLAT; + } + } + + @Override + public String toString() { + return "ButtonTypeConverter"; + } } \ No newline at end of file diff --git a/src/com/jfoenix/converters/DialogTransitionConverter.java b/jfoenix/src/main/java/com/jfoenix/converters/DialogTransitionConverter.java similarity index 97% rename from src/com/jfoenix/converters/DialogTransitionConverter.java rename to jfoenix/src/main/java/com/jfoenix/converters/DialogTransitionConverter.java index f660802c..6264cc26 100644 --- a/src/com/jfoenix/converters/DialogTransitionConverter.java +++ b/jfoenix/src/main/java/com/jfoenix/converters/DialogTransitionConverter.java @@ -1,60 +1,60 @@ -/* - * 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.converters; - -import com.jfoenix.controls.JFXDialog.DialogTransition; -import com.sun.javafx.css.StyleConverterImpl; -import javafx.css.ParsedValue; -import javafx.css.StyleConverter; -import javafx.scene.text.Font; - -/** - * Converts the CSS for -fx-dialog-transition items into DialogTransition. - * it's used in JFXDialog. - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class DialogTransitionConverter extends StyleConverterImpl { - // lazy, thread-safe instatiation - private static class Holder { - static final DialogTransitionConverter INSTANCE = new DialogTransitionConverter(); - } - public static StyleConverter getInstance() { - return Holder.INSTANCE; - } - private DialogTransitionConverter() { - super(); - } - - @Override - public DialogTransition convert(ParsedValue value, Font not_used) { - String string = value.getValue(); - try { - return DialogTransition.valueOf(string); - } catch (IllegalArgumentException | NullPointerException exception) { - return DialogTransition.CENTER; - } - } - @Override - public String toString() { - return "DialogTransitionConverter"; - } +/* + * 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.converters; + +import com.jfoenix.controls.JFXDialog.DialogTransition; +import com.sun.javafx.css.StyleConverterImpl; +import javafx.css.ParsedValue; +import javafx.css.StyleConverter; +import javafx.scene.text.Font; + +/** + * Converts the CSS for -fx-dialog-transition items into DialogTransition. + * it's used in JFXDialog. + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class DialogTransitionConverter extends StyleConverterImpl { + // lazy, thread-safe instatiation + private static class Holder { + static final DialogTransitionConverter INSTANCE = new DialogTransitionConverter(); + } + public static StyleConverter getInstance() { + return Holder.INSTANCE; + } + private DialogTransitionConverter() { + super(); + } + + @Override + public DialogTransition convert(ParsedValue value, Font not_used) { + String string = value.getValue(); + try { + return DialogTransition.valueOf(string); + } catch (IllegalArgumentException | NullPointerException exception) { + return DialogTransition.CENTER; + } + } + @Override + public String toString() { + return "DialogTransitionConverter"; + } } \ No newline at end of file diff --git a/src/com/jfoenix/converters/IndicatorPositionConverter.java b/jfoenix/src/main/java/com/jfoenix/converters/IndicatorPositionConverter.java similarity index 96% rename from src/com/jfoenix/converters/IndicatorPositionConverter.java rename to jfoenix/src/main/java/com/jfoenix/converters/IndicatorPositionConverter.java index 82d397ee..2c0f5e51 100644 --- a/src/com/jfoenix/converters/IndicatorPositionConverter.java +++ b/jfoenix/src/main/java/com/jfoenix/converters/IndicatorPositionConverter.java @@ -1,64 +1,64 @@ -/* - * 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.converters; - -import com.jfoenix.controls.JFXSlider.IndicatorPosition; -import com.sun.javafx.css.StyleConverterImpl; -import javafx.css.ParsedValue; -import javafx.css.StyleConverter; -import javafx.scene.text.Font; - -/** - * Converts the CSS for -fx-indicator-position items into IndicatorPosition. - * it's used in JFXSlider. - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class IndicatorPositionConverter extends StyleConverterImpl { - // lazy, thread-safe instatiation - private static class Holder { - static final IndicatorPositionConverter INSTANCE = new IndicatorPositionConverter(); - } - - public static StyleConverter getInstance() { - return Holder.INSTANCE; - } - - private IndicatorPositionConverter() { - super(); - } - - @Override - public IndicatorPosition convert(ParsedValue value, Font not_used) { - String string = value.getValue().toUpperCase(); - try { - return IndicatorPosition.valueOf(string); - } catch (IllegalArgumentException | NullPointerException exception) { - return IndicatorPosition.LEFT; - } - } - - @Override - public String toString() { - return "IndicatorPositionConverter"; - } - -} +/* + * 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.converters; + +import com.jfoenix.controls.JFXSlider.IndicatorPosition; +import com.sun.javafx.css.StyleConverterImpl; +import javafx.css.ParsedValue; +import javafx.css.StyleConverter; +import javafx.scene.text.Font; + +/** + * Converts the CSS for -fx-indicator-position items into IndicatorPosition. + * it's used in JFXSlider. + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class IndicatorPositionConverter extends StyleConverterImpl { + // lazy, thread-safe instatiation + private static class Holder { + static final IndicatorPositionConverter INSTANCE = new IndicatorPositionConverter(); + } + + public static StyleConverter getInstance() { + return Holder.INSTANCE; + } + + private IndicatorPositionConverter() { + super(); + } + + @Override + public IndicatorPosition convert(ParsedValue value, Font not_used) { + String string = value.getValue().toUpperCase(); + try { + return IndicatorPosition.valueOf(string); + } catch (IllegalArgumentException | NullPointerException exception) { + return IndicatorPosition.LEFT; + } + } + + @Override + public String toString() { + return "IndicatorPositionConverter"; + } + +} diff --git a/src/com/jfoenix/converters/RipplerMaskTypeConverter.java b/jfoenix/src/main/java/com/jfoenix/converters/RipplerMaskTypeConverter.java similarity index 97% rename from src/com/jfoenix/converters/RipplerMaskTypeConverter.java rename to jfoenix/src/main/java/com/jfoenix/converters/RipplerMaskTypeConverter.java index 11d6bf0c..a3b7185e 100644 --- a/src/com/jfoenix/converters/RipplerMaskTypeConverter.java +++ b/jfoenix/src/main/java/com/jfoenix/converters/RipplerMaskTypeConverter.java @@ -1,61 +1,61 @@ -/* - * 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.converters; - -import com.jfoenix.controls.JFXRippler.RipplerMask; -import com.sun.javafx.css.StyleConverterImpl; -import javafx.css.ParsedValue; -import javafx.css.StyleConverter; -import javafx.scene.text.Font; - -/** - * Converts the CSS for -fx-mask-type items into RipplerMask. - * it's used in JFXRippler. - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public final class RipplerMaskTypeConverter extends StyleConverterImpl { - // lazy, thread-safe instatiation - private static class Holder { - static final RipplerMaskTypeConverter INSTANCE = new RipplerMaskTypeConverter(); - } - public static StyleConverter getInstance() { - return Holder.INSTANCE; - } - private RipplerMaskTypeConverter() { - super(); - } - - @Override - public RipplerMask convert(ParsedValue value, Font not_used) { - String string = value.getValue(); - try { - return RipplerMask.valueOf(string); - } catch (IllegalArgumentException | NullPointerException exception) { - return RipplerMask.RECT; - } - } - - @Override - public String toString() { - return "RipplerMaskTypeConverter"; - } +/* + * 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.converters; + +import com.jfoenix.controls.JFXRippler.RipplerMask; +import com.sun.javafx.css.StyleConverterImpl; +import javafx.css.ParsedValue; +import javafx.css.StyleConverter; +import javafx.scene.text.Font; + +/** + * Converts the CSS for -fx-mask-type items into RipplerMask. + * it's used in JFXRippler. + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public final class RipplerMaskTypeConverter extends StyleConverterImpl { + // lazy, thread-safe instatiation + private static class Holder { + static final RipplerMaskTypeConverter INSTANCE = new RipplerMaskTypeConverter(); + } + public static StyleConverter getInstance() { + return Holder.INSTANCE; + } + private RipplerMaskTypeConverter() { + super(); + } + + @Override + public RipplerMask convert(ParsedValue value, Font not_used) { + String string = value.getValue(); + try { + return RipplerMask.valueOf(string); + } catch (IllegalArgumentException | NullPointerException exception) { + return RipplerMask.RECT; + } + } + + @Override + public String toString() { + return "RipplerMaskTypeConverter"; + } } \ No newline at end of file diff --git a/src/com/jfoenix/converters/base/NodeConverter.java b/jfoenix/src/main/java/com/jfoenix/converters/base/NodeConverter.java similarity index 97% rename from src/com/jfoenix/converters/base/NodeConverter.java rename to jfoenix/src/main/java/com/jfoenix/converters/base/NodeConverter.java index 7e14a6b5..3f382d39 100644 --- a/src/com/jfoenix/converters/base/NodeConverter.java +++ b/jfoenix/src/main/java/com/jfoenix/converters/base/NodeConverter.java @@ -1,53 +1,53 @@ -/* - * 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.converters.base; - -import javafx.scene.Node; - -/** - * Converter defines conversion behavior between Nodes and Objects. - * The type of Objects are defined by the subclasses of Converter. - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public abstract class NodeConverter { - /** - * Converts the object provided into its node form. - * Styling of the returned node is defined by the specific converter. - * @return a node representation of the object passed in. - */ - public abstract Node toNode(T object); - - /** - * Converts the node provided into an object defined by the specific converter. - * Format of the node and type of the resulting object is defined by the specific converter. - * @return an object representation of the node passed in. - */ - public abstract T fromNode(Node node); - - /** - * Converts the object provided into a String defined by the specific converter. - * Format of the String is defined by the specific converter. - * @return a String representation of the node passed in. - */ - public abstract String toString(T object); - +/* + * 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.converters.base; + +import javafx.scene.Node; + +/** + * Converter defines conversion behavior between Nodes and Objects. + * The type of Objects are defined by the subclasses of Converter. + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public abstract class NodeConverter { + /** + * Converts the object provided into its node form. + * Styling of the returned node is defined by the specific converter. + * @return a node representation of the object passed in. + */ + public abstract Node toNode(T object); + + /** + * Converts the node provided into an object defined by the specific converter. + * Format of the node and type of the resulting object is defined by the specific converter. + * @return an object representation of the node passed in. + */ + public abstract T fromNode(Node node); + + /** + * Converts the object provided into a String defined by the specific converter. + * Format of the String is defined by the specific converter. + * @return a String representation of the node passed in. + */ + public abstract String toString(T object); + } \ No newline at end of file diff --git a/src/com/jfoenix/effects/JFXDepthManager.java b/jfoenix/src/main/java/com/jfoenix/effects/JFXDepthManager.java similarity index 97% rename from src/com/jfoenix/effects/JFXDepthManager.java rename to jfoenix/src/main/java/com/jfoenix/effects/JFXDepthManager.java index df986ec3..6fe3d448 100644 --- a/src/com/jfoenix/effects/JFXDepthManager.java +++ b/jfoenix/src/main/java/com/jfoenix/effects/JFXDepthManager.java @@ -1,83 +1,83 @@ -/* - * 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.effects; - -import javafx.scene.Node; -import javafx.scene.effect.BlurType; -import javafx.scene.effect.DropShadow; -import javafx.scene.layout.Pane; -import javafx.scene.paint.Color; - -/** - * it will create a shadow effect for a given node and a specified depth level. - * depth levels are {0,1,2,3,4,5} - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXDepthManager { - - private static DropShadow[] depth = new DropShadow[]{ - new DropShadow(BlurType.GAUSSIAN, Color.rgb(0,0,0,0), 0, 0, 0, 0), - new DropShadow(BlurType.GAUSSIAN, Color.rgb(0,0,0,0.26), 10, 0.12, -1, 2), - new DropShadow(BlurType.GAUSSIAN, Color.rgb(0,0,0,0.26), 15, 0.16, 0, 4), - new DropShadow(BlurType.GAUSSIAN, Color.rgb(0,0,0,0.26), 20, 0.19, 0, 6), - new DropShadow(BlurType.GAUSSIAN, Color.rgb(0,0,0,0.26), 25, 0.25, 0, 8), - new DropShadow(BlurType.GAUSSIAN, Color.rgb(0,0,0,0.26), 30, 0.30, 0, 10)}; - - /** - * this method is used to add shadow effect to the node, - * however the shadow is not real ( gets affected with node transformations) - * - * use {@link #createMaterialNode()} instead to generate a real shadow - */ - public static void setDepth(Node control, int level){ - level = level < 0 ? 0 : level; - level = level > 5 ? 5 : level; - control.setEffect(new DropShadow(BlurType.GAUSSIAN, depth[level].getColor() ,depth[level].getRadius(),depth[level].getSpread(),depth[level].getOffsetX(),depth[level].getOffsetY())); - } - - public static int getLevels(){ - return depth.length; - } - - public static DropShadow getShadowAt(int level){ - return depth[level]; - } - - /** - * this method will generate a new container node that prevent - * control transformation to be applied to the shadow effect - * (which makes it looks as a real shadow) - */ - public static Node createMaterialNode(Node control, int level){ - Node container = new Pane(control); - container.getStyleClass().add("depth-container"); - level = level < 0 ? 0 : level; - level = level > 5 ? 5 : level; - container.setEffect(new DropShadow(BlurType.GAUSSIAN, depth[level].getColor() ,depth[level].getRadius(),depth[level].getSpread(),depth[level].getOffsetX(),depth[level].getOffsetY())); - return container; - } - - public static void pop(Node control){ - control.setEffect(new DropShadow(BlurType.GAUSSIAN, Color.rgb(0,0,0,0.26) ,5, 0.05, 0, 1)); - } - -} +/* + * 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.effects; + +import javafx.scene.Node; +import javafx.scene.effect.BlurType; +import javafx.scene.effect.DropShadow; +import javafx.scene.layout.Pane; +import javafx.scene.paint.Color; + +/** + * it will create a shadow effect for a given node and a specified depth level. + * depth levels are {0,1,2,3,4,5} + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXDepthManager { + + private static DropShadow[] depth = new DropShadow[]{ + new DropShadow(BlurType.GAUSSIAN, Color.rgb(0,0,0,0), 0, 0, 0, 0), + new DropShadow(BlurType.GAUSSIAN, Color.rgb(0,0,0,0.26), 10, 0.12, -1, 2), + new DropShadow(BlurType.GAUSSIAN, Color.rgb(0,0,0,0.26), 15, 0.16, 0, 4), + new DropShadow(BlurType.GAUSSIAN, Color.rgb(0,0,0,0.26), 20, 0.19, 0, 6), + new DropShadow(BlurType.GAUSSIAN, Color.rgb(0,0,0,0.26), 25, 0.25, 0, 8), + new DropShadow(BlurType.GAUSSIAN, Color.rgb(0,0,0,0.26), 30, 0.30, 0, 10)}; + + /** + * this method is used to add shadow effect to the node, + * however the shadow is not real ( gets affected with node transformations) + * + * use {@link #createMaterialNode()} instead to generate a real shadow + */ + public static void setDepth(Node control, int level){ + level = level < 0 ? 0 : level; + level = level > 5 ? 5 : level; + control.setEffect(new DropShadow(BlurType.GAUSSIAN, depth[level].getColor() ,depth[level].getRadius(),depth[level].getSpread(),depth[level].getOffsetX(),depth[level].getOffsetY())); + } + + public static int getLevels(){ + return depth.length; + } + + public static DropShadow getShadowAt(int level){ + return depth[level]; + } + + /** + * this method will generate a new container node that prevent + * control transformation to be applied to the shadow effect + * (which makes it looks as a real shadow) + */ + public static Node createMaterialNode(Node control, int level){ + Node container = new Pane(control); + container.getStyleClass().add("depth-container"); + level = level < 0 ? 0 : level; + level = level > 5 ? 5 : level; + container.setEffect(new DropShadow(BlurType.GAUSSIAN, depth[level].getColor() ,depth[level].getRadius(),depth[level].getSpread(),depth[level].getOffsetX(),depth[level].getOffsetY())); + return container; + } + + public static void pop(Node control){ + control.setEffect(new DropShadow(BlurType.GAUSSIAN, Color.rgb(0,0,0,0.26) ,5, 0.05, 0, 1)); + } + +} diff --git a/src/com/jfoenix/responsive/JFXResponsiveHandler.java b/jfoenix/src/main/java/com/jfoenix/responsive/JFXResponsiveHandler.java similarity index 97% rename from src/com/jfoenix/responsive/JFXResponsiveHandler.java rename to jfoenix/src/main/java/com/jfoenix/responsive/JFXResponsiveHandler.java index 74f04c6d..f178c1a1 100644 --- a/src/com/jfoenix/responsive/JFXResponsiveHandler.java +++ b/jfoenix/src/main/java/com/jfoenix/responsive/JFXResponsiveHandler.java @@ -1,120 +1,120 @@ -/* - * 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.responsive; - -import javafx.collections.ListChangeListener; -import javafx.css.PseudoClass; -import javafx.scene.Node; -import javafx.scene.Parent; -import javafx.scene.control.Control; -import javafx.scene.control.ScrollPane; -import javafx.scene.layout.Pane; -import javafx.stage.Stage; - -/** - * Responsive handler will scan all nodes in the scene and add a certain - * pseudo class (style class) to them according to the device ( screen size ) - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXResponsiveHandler { - - public static final PseudoClass PSEUDO_CLASS_EX_SMALL = PseudoClass.getPseudoClass("extreme-small-device"); - public static final PseudoClass PSEUDO_CLASS_SMALL = PseudoClass.getPseudoClass("small-device"); - public static final PseudoClass PSEUDO_CLASS_MEDIUM = PseudoClass.getPseudoClass("medium-device"); - public static final PseudoClass PSEUDO_CLASS_LARGE = PseudoClass.getPseudoClass("large-device"); - - /** - * Construct a responsive handler for a specified Stage and css class. - *

- * Device css classes can be one of the following: - *

    - *
  • {@link JFXResponsiveHandler#PSEUDO_CLASS_EX_SMALL}
  • - *
  • {@link JFXResponsiveHandler#PSEUDO_CLASS_LARGE}
  • - *
  • {@link JFXResponsiveHandler#PSEUDO_CLASS_MEDIUM}
  • - *
  • {@link JFXResponsiveHandler#PSEUDO_CLASS_SMALL}
  • - *
- * - * Note: the css class must be chosen by the user according to a device - * detection methodology - * - * @param stage the JavaFX Application stage - * @param pseudoClass css class for certain device - */ - public JFXResponsiveHandler(Stage stage, PseudoClass pseudoClass) { - scanAllNodes(stage.getScene().getRoot(), PSEUDO_CLASS_LARGE); - } - - /** - * scans all nodes in the scene and apply the css pseduoClass to them. - * - * @param parent stage parent node - * @param pseudoClass css class for certain device - */ - private void scanAllNodes(Parent parent, PseudoClass pseudoClass){ - parent.getChildrenUnmodifiable().addListener(new ListChangeListener(){ - @Override - public void onChanged(javafx.collections.ListChangeListener.Change c) { - while (c.next()) - if (!c.wasPermutated() && !c.wasUpdated()) - for (Node addedNode : c.getAddedSubList()) - if(addedNode instanceof Parent) - scanAllNodes((Parent) addedNode,pseudoClass); - } - }); - for (Node component : parent.getChildrenUnmodifiable()) { - if (component instanceof Pane) { - ((Pane)component).getChildren().addListener(new ListChangeListener(){ - @Override - public void onChanged(javafx.collections.ListChangeListener.Change c) { - while (c.next()) { - if (!c.wasPermutated() && !c.wasUpdated()) { - for (Node addedNode : c.getAddedSubList()) { - if(addedNode instanceof Parent) - scanAllNodes((Parent) addedNode,pseudoClass); - } - } - } - } - }); - //if the component is a container, scan its children - scanAllNodes((Pane) component, pseudoClass); - } else if (component instanceof ScrollPane){ - ((ScrollPane)component).contentProperty().addListener((o,oldVal,newVal)-> { - scanAllNodes((Parent) newVal,pseudoClass); - }); - //if the component is a container, scan its children - if(((ScrollPane)component).getContent() instanceof Parent){ - - scanAllNodes((Parent) ((ScrollPane)component).getContent(), pseudoClass); - } - } else if (component instanceof Control) { - //if the component is an instance of IInputControl, add to list - ((Control)component).pseudoClassStateChanged(PSEUDO_CLASS_EX_SMALL, pseudoClass == PSEUDO_CLASS_EX_SMALL); - ((Control)component).pseudoClassStateChanged(PSEUDO_CLASS_SMALL, pseudoClass == PSEUDO_CLASS_SMALL); - ((Control)component).pseudoClassStateChanged(PSEUDO_CLASS_MEDIUM, pseudoClass == PSEUDO_CLASS_MEDIUM); - ((Control)component).pseudoClassStateChanged(PSEUDO_CLASS_LARGE, pseudoClass == PSEUDO_CLASS_LARGE); - } - } - } - - -} +/* + * 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.responsive; + +import javafx.collections.ListChangeListener; +import javafx.css.PseudoClass; +import javafx.scene.Node; +import javafx.scene.Parent; +import javafx.scene.control.Control; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.Pane; +import javafx.stage.Stage; + +/** + * Responsive handler will scan all nodes in the scene and add a certain + * pseudo class (style class) to them according to the device ( screen size ) + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXResponsiveHandler { + + public static final PseudoClass PSEUDO_CLASS_EX_SMALL = PseudoClass.getPseudoClass("extreme-small-device"); + public static final PseudoClass PSEUDO_CLASS_SMALL = PseudoClass.getPseudoClass("small-device"); + public static final PseudoClass PSEUDO_CLASS_MEDIUM = PseudoClass.getPseudoClass("medium-device"); + public static final PseudoClass PSEUDO_CLASS_LARGE = PseudoClass.getPseudoClass("large-device"); + + /** + * Construct a responsive handler for a specified Stage and css class. + *

+ * Device css classes can be one of the following: + *

    + *
  • {@link JFXResponsiveHandler#PSEUDO_CLASS_EX_SMALL}
  • + *
  • {@link JFXResponsiveHandler#PSEUDO_CLASS_LARGE}
  • + *
  • {@link JFXResponsiveHandler#PSEUDO_CLASS_MEDIUM}
  • + *
  • {@link JFXResponsiveHandler#PSEUDO_CLASS_SMALL}
  • + *
+ * + * Note: the css class must be chosen by the user according to a device + * detection methodology + * + * @param stage the JavaFX Application stage + * @param pseudoClass css class for certain device + */ + public JFXResponsiveHandler(Stage stage, PseudoClass pseudoClass) { + scanAllNodes(stage.getScene().getRoot(), PSEUDO_CLASS_LARGE); + } + + /** + * scans all nodes in the scene and apply the css pseduoClass to them. + * + * @param parent stage parent node + * @param pseudoClass css class for certain device + */ + private void scanAllNodes(Parent parent, PseudoClass pseudoClass){ + parent.getChildrenUnmodifiable().addListener(new ListChangeListener(){ + @Override + public void onChanged(javafx.collections.ListChangeListener.Change c) { + while (c.next()) + if (!c.wasPermutated() && !c.wasUpdated()) + for (Node addedNode : c.getAddedSubList()) + if(addedNode instanceof Parent) + scanAllNodes((Parent) addedNode,pseudoClass); + } + }); + for (Node component : parent.getChildrenUnmodifiable()) { + if (component instanceof Pane) { + ((Pane)component).getChildren().addListener(new ListChangeListener(){ + @Override + public void onChanged(javafx.collections.ListChangeListener.Change c) { + while (c.next()) { + if (!c.wasPermutated() && !c.wasUpdated()) { + for (Node addedNode : c.getAddedSubList()) { + if(addedNode instanceof Parent) + scanAllNodes((Parent) addedNode,pseudoClass); + } + } + } + } + }); + //if the component is a container, scan its children + scanAllNodes((Pane) component, pseudoClass); + } else if (component instanceof ScrollPane){ + ((ScrollPane)component).contentProperty().addListener((o,oldVal,newVal)-> { + scanAllNodes((Parent) newVal,pseudoClass); + }); + //if the component is a container, scan its children + if(((ScrollPane)component).getContent() instanceof Parent){ + + scanAllNodes((Parent) ((ScrollPane)component).getContent(), pseudoClass); + } + } else if (component instanceof Control) { + //if the component is an instance of IInputControl, add to list + ((Control)component).pseudoClassStateChanged(PSEUDO_CLASS_EX_SMALL, pseudoClass == PSEUDO_CLASS_EX_SMALL); + ((Control)component).pseudoClassStateChanged(PSEUDO_CLASS_SMALL, pseudoClass == PSEUDO_CLASS_SMALL); + ((Control)component).pseudoClassStateChanged(PSEUDO_CLASS_MEDIUM, pseudoClass == PSEUDO_CLASS_MEDIUM); + ((Control)component).pseudoClassStateChanged(PSEUDO_CLASS_LARGE, pseudoClass == PSEUDO_CLASS_LARGE); + } + } + } + + +} diff --git a/src/com/jfoenix/skins/JFXButtonSkin.java b/jfoenix/src/main/java/com/jfoenix/skins/JFXButtonSkin.java similarity index 97% rename from src/com/jfoenix/skins/JFXButtonSkin.java rename to jfoenix/src/main/java/com/jfoenix/skins/JFXButtonSkin.java index 0b5dff47..ff31d5e5 100644 --- a/src/com/jfoenix/skins/JFXButtonSkin.java +++ b/jfoenix/src/main/java/com/jfoenix/skins/JFXButtonSkin.java @@ -1,251 +1,251 @@ -/* - * 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.skins; - -import com.jfoenix.controls.JFXButton; -import com.jfoenix.controls.JFXButton.ButtonType; -import com.jfoenix.controls.JFXRippler; -import com.jfoenix.effects.JFXDepthManager; -import com.jfoenix.transitions.CachedTransition; -import com.sun.javafx.scene.control.skin.ButtonSkin; -import com.sun.javafx.scene.control.skin.LabeledText; -import javafx.animation.*; -import javafx.beans.binding.Bindings; -import javafx.geometry.Insets; -import javafx.scene.Node; -import javafx.scene.control.Label; -import javafx.scene.effect.DropShadow; -import javafx.scene.layout.Background; -import javafx.scene.layout.BackgroundFill; -import javafx.scene.layout.CornerRadii; -import javafx.scene.layout.StackPane; -import javafx.scene.paint.Color; -import javafx.util.Duration; - -/** - *

Material Design Button Skin

- * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXButtonSkin extends ButtonSkin { - - private final StackPane buttonContainer = new StackPane(); - private JFXRippler buttonRippler; - private Transition clickedAnimation ; - private final CornerRadii defaultRadii = new CornerRadii(3); - - private boolean invalid = true; - private Runnable releaseManualRippler = null; - public JFXButtonSkin(JFXButton button) { - super(button); - - buttonRippler = new JFXRippler(new StackPane()){ - @Override protected Node getMask(){ - StackPane mask = new StackPane(); - mask.shapeProperty().bind(buttonContainer.shapeProperty()); - mask.backgroundProperty().bind(Bindings.createObjectBinding(()->{ - return new Background(new BackgroundFill(Color.WHITE, - buttonContainer.backgroundProperty().get()!=null && buttonContainer.getBackground().getFills().size() > 0 ?buttonContainer.getBackground().getFills().get(0).getRadii() : defaultRadii, - buttonContainer.backgroundProperty().get()!=null && buttonContainer.getBackground().getFills().size() > 0 ?buttonContainer.getBackground().getFills().get(0).getInsets() : Insets.EMPTY)); - }, buttonContainer.backgroundProperty())); - mask.resize(buttonContainer.getWidth()-buttonContainer.snappedRightInset()-buttonContainer.snappedLeftInset(), buttonContainer.getHeight()-buttonContainer.snappedBottomInset()-buttonContainer.snappedTopInset()); - return mask; - } - @Override protected void initListeners(){ - ripplerPane.setOnMousePressed((event) -> { - if(releaseManualRippler!=null) releaseManualRippler.run(); - releaseManualRippler = null; - createRipple(event.getX(),event.getY()); - }); - } - }; - - getSkinnable().armedProperty().addListener((o,oldVal,newVal)->{ - if(newVal){ - releaseManualRippler = buttonRippler.createManualRipple(); - if(clickedAnimation!=null){ - clickedAnimation.setRate(1); - clickedAnimation.play(); - } - }else{ - if(releaseManualRippler!=null) releaseManualRippler.run(); - if(clickedAnimation!=null){ - clickedAnimation.setRate(-1); - clickedAnimation.play(); - } - } - }); - - buttonContainer.getChildren().add(buttonRippler); - - - // add listeners to the button and bind properties - button.buttonTypeProperty().addListener((o,oldVal,newVal)->updateButtonType(newVal)); - button.setOnMousePressed((e)->{ - if(clickedAnimation!=null){ - clickedAnimation.setRate(1); - clickedAnimation.play(); - } - }); - button.setOnMouseReleased((e)->{ - if(clickedAnimation!=null){ - clickedAnimation.setRate(-1); - clickedAnimation.play(); - } - }); - - // show focused state - button.focusedProperty().addListener((o,oldVal,newVal)->{ - if(newVal){ - if(!getSkinnable().isPressed()) buttonRippler.showOverlay(); - }else buttonRippler.hideOverlay(); - }); - button.pressedProperty().addListener((o,oldVal,newVal)-> buttonRippler.hideOverlay()); - - /* - * disable action when clicking on the button shadow - */ - button.setPickOnBounds(false); - buttonContainer.setPickOnBounds(false); - buttonContainer.shapeProperty().bind(getSkinnable().shapeProperty()); - buttonContainer.borderProperty().bind(getSkinnable().borderProperty()); - buttonContainer.backgroundProperty().bind(Bindings.createObjectBinding(()->{ - // reset button background to transparent if its set to java default values - if(button.getBackground() == null || isJavaDefaultBackground(button.getBackground()) || isJavaDefaultClickedBackground(button.getBackground()) ) - button.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, defaultRadii, null))); - try{ - //Insets always Empty - if(getSkinnable().getBackground()!=null && getSkinnable().getBackground().getFills().get(0).getInsets().equals(new Insets(-0.2,-0.2, -0.2,-0.2))){ - return new Background(new BackgroundFill(getSkinnable().getBackground()!=null?getSkinnable().getBackground().getFills().get(0).getFill() : Color.TRANSPARENT, - getSkinnable().backgroundProperty().get()!=null?getSkinnable().getBackground().getFills().get(0).getRadii() : defaultRadii, - Insets.EMPTY/*new Insets(0,0,-1.0,0)*/)); - }else{ - return new Background(new BackgroundFill(getSkinnable().getBackground()!=null?getSkinnable().getBackground().getFills().get(0).getFill() : Color.TRANSPARENT, - getSkinnable().getBackground()!=null?getSkinnable().getBackground().getFills().get(0).getRadii() : defaultRadii, - Insets.EMPTY/*getSkinnable().backgroundProperty().get()!=null?getSkinnable().getBackground().getFills().get(0).getInsets() : Insets.EMPTY*/)); - } - }catch(Exception e){ - return getSkinnable().getBackground(); - } - }, getSkinnable().backgroundProperty())); - - button.ripplerFillProperty().addListener((o,oldVal,newVal)-> buttonRippler.setRipplerFill(newVal)); - - // set default background to transparent - if(button.getBackground() == null || isJavaDefaultBackground(button.getBackground())) - button.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, defaultRadii, null))); - - updateButtonType(button.getButtonType()); - updateChildren(); - } - - @Override - protected void updateChildren() { - super.updateChildren(); - if (buttonContainer != null) getChildren().add(0,buttonContainer); - for(int i = 1 ; i < getChildren().size(); i++) - getChildren().get(i).setMouseTransparent(true); - } - - @Override - protected void layoutChildren(final double x, final double y, final double w, final double h) { - if(invalid){ - if(((JFXButton)getSkinnable()).getRipplerFill() == null){ - // change rippler fill according to the last LabeledText/Label child - for(int i = getChildren().size()-1; i >= 1; i--){ - if(getChildren().get(i) instanceof LabeledText){ - buttonRippler.setRipplerFill(((LabeledText)getChildren().get(i)).getFill()); - ((LabeledText)getChildren().get(i)).fillProperty().addListener((o,oldVal,newVal)-> buttonRippler.setRipplerFill(newVal)); - break; - }else if(getChildren().get(i) instanceof Label){ - buttonRippler.setRipplerFill(((Label)getChildren().get(i)).getTextFill()); - ((Label)getChildren().get(i)).textFillProperty().addListener((o,oldVal,newVal)-> buttonRippler.setRipplerFill(newVal)); - break; - } - } - }else{ - buttonRippler.setRipplerFill(((JFXButton)getSkinnable()).getRipplerFill()); - } - invalid = false; - } - double shift = 1; - buttonContainer.resizeRelocate(getSkinnable().getLayoutBounds().getMinX()-shift, getSkinnable().getLayoutBounds().getMinY()-shift, getSkinnable().getWidth()+(2*shift), getSkinnable().getHeight()+(2*shift)); - layoutLabelInArea(x, y, w, h); - } - - private boolean isJavaDefaultBackground(Background background){ - try{ - return background.getFills().get(0).getFill().toString().equals("0xffffffba") - || background.getFills().get(0).getFill().toString().equals("0xffffffbf") - || background.getFills().get(0).getFill().toString().equals("0xffffffbd"); - }catch(Exception e){ - return false; - } - } - - private boolean isJavaDefaultClickedBackground(Background background){ - try{ - return background.getFills().get(0).getFill().toString().equals("0x039ed3ff"); - }catch(Exception e){ - return false; - } - } - - private void updateButtonType(ButtonType type){ - switch (type) { - case RAISED: - JFXDepthManager.setDepth(buttonContainer, 2); - clickedAnimation = new ButtonClickTransition(); - break; - default: - buttonContainer.setEffect(null); - break; - } - } - - - private class ButtonClickTransition extends CachedTransition { - - public ButtonClickTransition() { - super(buttonContainer, new Timeline( - new KeyFrame(Duration.ZERO, - new KeyValue(((DropShadow)buttonContainer.getEffect()).radiusProperty(), JFXDepthManager.getShadowAt(2).radiusProperty().get(), Interpolator.EASE_BOTH), - new KeyValue(((DropShadow)buttonContainer.getEffect()).spreadProperty(), JFXDepthManager.getShadowAt(2).spreadProperty().get(), Interpolator.EASE_BOTH), - new KeyValue(((DropShadow)buttonContainer.getEffect()).offsetXProperty(), JFXDepthManager.getShadowAt(2).offsetXProperty().get(), Interpolator.EASE_BOTH), - new KeyValue(((DropShadow)buttonContainer.getEffect()).offsetYProperty(), JFXDepthManager.getShadowAt(2).offsetYProperty().get(), Interpolator.EASE_BOTH) - ), - new KeyFrame(Duration.millis(1000), - new KeyValue(((DropShadow)buttonContainer.getEffect()).radiusProperty(), JFXDepthManager.getShadowAt(5).radiusProperty().get(), Interpolator.EASE_BOTH), - new KeyValue(((DropShadow)buttonContainer.getEffect()).spreadProperty(), JFXDepthManager.getShadowAt(5).spreadProperty().get(), Interpolator.EASE_BOTH), - new KeyValue(((DropShadow)buttonContainer.getEffect()).offsetXProperty(), JFXDepthManager.getShadowAt(5).offsetXProperty().get(), Interpolator.EASE_BOTH), - new KeyValue(((DropShadow)buttonContainer.getEffect()).offsetYProperty(), JFXDepthManager.getShadowAt(5).offsetYProperty().get(), Interpolator.EASE_BOTH) - ) - ) - ); - // reduce the number to increase the shifting , increase number to reduce shifting - setCycleDuration(Duration.seconds(0.2)); - setDelay(Duration.seconds(0)); - } - - } - - -} +/* + * 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.skins; + +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXButton.ButtonType; +import com.jfoenix.controls.JFXRippler; +import com.jfoenix.effects.JFXDepthManager; +import com.jfoenix.transitions.CachedTransition; +import com.sun.javafx.scene.control.skin.ButtonSkin; +import com.sun.javafx.scene.control.skin.LabeledText; +import javafx.animation.*; +import javafx.beans.binding.Bindings; +import javafx.geometry.Insets; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.effect.DropShadow; +import javafx.scene.layout.Background; +import javafx.scene.layout.BackgroundFill; +import javafx.scene.layout.CornerRadii; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; +import javafx.util.Duration; + +/** + *

Material Design Button Skin

+ * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXButtonSkin extends ButtonSkin { + + private final StackPane buttonContainer = new StackPane(); + private JFXRippler buttonRippler; + private Transition clickedAnimation ; + private final CornerRadii defaultRadii = new CornerRadii(3); + + private boolean invalid = true; + private Runnable releaseManualRippler = null; + public JFXButtonSkin(JFXButton button) { + super(button); + + buttonRippler = new JFXRippler(new StackPane()){ + @Override protected Node getMask(){ + StackPane mask = new StackPane(); + mask.shapeProperty().bind(buttonContainer.shapeProperty()); + mask.backgroundProperty().bind(Bindings.createObjectBinding(()->{ + return new Background(new BackgroundFill(Color.WHITE, + buttonContainer.backgroundProperty().get()!=null && buttonContainer.getBackground().getFills().size() > 0 ?buttonContainer.getBackground().getFills().get(0).getRadii() : defaultRadii, + buttonContainer.backgroundProperty().get()!=null && buttonContainer.getBackground().getFills().size() > 0 ?buttonContainer.getBackground().getFills().get(0).getInsets() : Insets.EMPTY)); + }, buttonContainer.backgroundProperty())); + mask.resize(buttonContainer.getWidth()-buttonContainer.snappedRightInset()-buttonContainer.snappedLeftInset(), buttonContainer.getHeight()-buttonContainer.snappedBottomInset()-buttonContainer.snappedTopInset()); + return mask; + } + @Override protected void initListeners(){ + ripplerPane.setOnMousePressed((event) -> { + if(releaseManualRippler!=null) releaseManualRippler.run(); + releaseManualRippler = null; + createRipple(event.getX(),event.getY()); + }); + } + }; + + getSkinnable().armedProperty().addListener((o,oldVal,newVal)->{ + if(newVal){ + releaseManualRippler = buttonRippler.createManualRipple(); + if(clickedAnimation!=null){ + clickedAnimation.setRate(1); + clickedAnimation.play(); + } + }else{ + if(releaseManualRippler!=null) releaseManualRippler.run(); + if(clickedAnimation!=null){ + clickedAnimation.setRate(-1); + clickedAnimation.play(); + } + } + }); + + buttonContainer.getChildren().add(buttonRippler); + + + // add listeners to the button and bind properties + button.buttonTypeProperty().addListener((o,oldVal,newVal)->updateButtonType(newVal)); + button.setOnMousePressed((e)->{ + if(clickedAnimation!=null){ + clickedAnimation.setRate(1); + clickedAnimation.play(); + } + }); + button.setOnMouseReleased((e)->{ + if(clickedAnimation!=null){ + clickedAnimation.setRate(-1); + clickedAnimation.play(); + } + }); + + // show focused state + button.focusedProperty().addListener((o,oldVal,newVal)->{ + if(newVal){ + if(!getSkinnable().isPressed()) buttonRippler.showOverlay(); + }else buttonRippler.hideOverlay(); + }); + button.pressedProperty().addListener((o,oldVal,newVal)-> buttonRippler.hideOverlay()); + + /* + * disable action when clicking on the button shadow + */ + button.setPickOnBounds(false); + buttonContainer.setPickOnBounds(false); + buttonContainer.shapeProperty().bind(getSkinnable().shapeProperty()); + buttonContainer.borderProperty().bind(getSkinnable().borderProperty()); + buttonContainer.backgroundProperty().bind(Bindings.createObjectBinding(()->{ + // reset button background to transparent if its set to java default values + if(button.getBackground() == null || isJavaDefaultBackground(button.getBackground()) || isJavaDefaultClickedBackground(button.getBackground()) ) + button.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, defaultRadii, null))); + try{ + //Insets always Empty + if(getSkinnable().getBackground()!=null && getSkinnable().getBackground().getFills().get(0).getInsets().equals(new Insets(-0.2,-0.2, -0.2,-0.2))){ + return new Background(new BackgroundFill(getSkinnable().getBackground()!=null?getSkinnable().getBackground().getFills().get(0).getFill() : Color.TRANSPARENT, + getSkinnable().backgroundProperty().get()!=null?getSkinnable().getBackground().getFills().get(0).getRadii() : defaultRadii, + Insets.EMPTY/*new Insets(0,0,-1.0,0)*/)); + }else{ + return new Background(new BackgroundFill(getSkinnable().getBackground()!=null?getSkinnable().getBackground().getFills().get(0).getFill() : Color.TRANSPARENT, + getSkinnable().getBackground()!=null?getSkinnable().getBackground().getFills().get(0).getRadii() : defaultRadii, + Insets.EMPTY/*getSkinnable().backgroundProperty().get()!=null?getSkinnable().getBackground().getFills().get(0).getInsets() : Insets.EMPTY*/)); + } + }catch(Exception e){ + return getSkinnable().getBackground(); + } + }, getSkinnable().backgroundProperty())); + + button.ripplerFillProperty().addListener((o,oldVal,newVal)-> buttonRippler.setRipplerFill(newVal)); + + // set default background to transparent + if(button.getBackground() == null || isJavaDefaultBackground(button.getBackground())) + button.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, defaultRadii, null))); + + updateButtonType(button.getButtonType()); + updateChildren(); + } + + @Override + protected void updateChildren() { + super.updateChildren(); + if (buttonContainer != null) getChildren().add(0,buttonContainer); + for(int i = 1 ; i < getChildren().size(); i++) + getChildren().get(i).setMouseTransparent(true); + } + + @Override + protected void layoutChildren(final double x, final double y, final double w, final double h) { + if(invalid){ + if(((JFXButton)getSkinnable()).getRipplerFill() == null){ + // change rippler fill according to the last LabeledText/Label child + for(int i = getChildren().size()-1; i >= 1; i--){ + if(getChildren().get(i) instanceof LabeledText){ + buttonRippler.setRipplerFill(((LabeledText)getChildren().get(i)).getFill()); + ((LabeledText)getChildren().get(i)).fillProperty().addListener((o,oldVal,newVal)-> buttonRippler.setRipplerFill(newVal)); + break; + }else if(getChildren().get(i) instanceof Label){ + buttonRippler.setRipplerFill(((Label)getChildren().get(i)).getTextFill()); + ((Label)getChildren().get(i)).textFillProperty().addListener((o,oldVal,newVal)-> buttonRippler.setRipplerFill(newVal)); + break; + } + } + }else{ + buttonRippler.setRipplerFill(((JFXButton)getSkinnable()).getRipplerFill()); + } + invalid = false; + } + double shift = 1; + buttonContainer.resizeRelocate(getSkinnable().getLayoutBounds().getMinX()-shift, getSkinnable().getLayoutBounds().getMinY()-shift, getSkinnable().getWidth()+(2*shift), getSkinnable().getHeight()+(2*shift)); + layoutLabelInArea(x, y, w, h); + } + + private boolean isJavaDefaultBackground(Background background){ + try{ + return background.getFills().get(0).getFill().toString().equals("0xffffffba") + || background.getFills().get(0).getFill().toString().equals("0xffffffbf") + || background.getFills().get(0).getFill().toString().equals("0xffffffbd"); + }catch(Exception e){ + return false; + } + } + + private boolean isJavaDefaultClickedBackground(Background background){ + try{ + return background.getFills().get(0).getFill().toString().equals("0x039ed3ff"); + }catch(Exception e){ + return false; + } + } + + private void updateButtonType(ButtonType type){ + switch (type) { + case RAISED: + JFXDepthManager.setDepth(buttonContainer, 2); + clickedAnimation = new ButtonClickTransition(); + break; + default: + buttonContainer.setEffect(null); + break; + } + } + + + private class ButtonClickTransition extends CachedTransition { + + public ButtonClickTransition() { + super(buttonContainer, new Timeline( + new KeyFrame(Duration.ZERO, + new KeyValue(((DropShadow)buttonContainer.getEffect()).radiusProperty(), JFXDepthManager.getShadowAt(2).radiusProperty().get(), Interpolator.EASE_BOTH), + new KeyValue(((DropShadow)buttonContainer.getEffect()).spreadProperty(), JFXDepthManager.getShadowAt(2).spreadProperty().get(), Interpolator.EASE_BOTH), + new KeyValue(((DropShadow)buttonContainer.getEffect()).offsetXProperty(), JFXDepthManager.getShadowAt(2).offsetXProperty().get(), Interpolator.EASE_BOTH), + new KeyValue(((DropShadow)buttonContainer.getEffect()).offsetYProperty(), JFXDepthManager.getShadowAt(2).offsetYProperty().get(), Interpolator.EASE_BOTH) + ), + new KeyFrame(Duration.millis(1000), + new KeyValue(((DropShadow)buttonContainer.getEffect()).radiusProperty(), JFXDepthManager.getShadowAt(5).radiusProperty().get(), Interpolator.EASE_BOTH), + new KeyValue(((DropShadow)buttonContainer.getEffect()).spreadProperty(), JFXDepthManager.getShadowAt(5).spreadProperty().get(), Interpolator.EASE_BOTH), + new KeyValue(((DropShadow)buttonContainer.getEffect()).offsetXProperty(), JFXDepthManager.getShadowAt(5).offsetXProperty().get(), Interpolator.EASE_BOTH), + new KeyValue(((DropShadow)buttonContainer.getEffect()).offsetYProperty(), JFXDepthManager.getShadowAt(5).offsetYProperty().get(), Interpolator.EASE_BOTH) + ) + ) + ); + // reduce the number to increase the shifting , increase number to reduce shifting + setCycleDuration(Duration.seconds(0.2)); + setDelay(Duration.seconds(0)); + } + + } + + +} diff --git a/src/com/jfoenix/skins/JFXCheckBoxOldSkin.java b/jfoenix/src/main/java/com/jfoenix/skins/JFXCheckBoxOldSkin.java similarity index 97% rename from src/com/jfoenix/skins/JFXCheckBoxOldSkin.java rename to jfoenix/src/main/java/com/jfoenix/skins/JFXCheckBoxOldSkin.java index 7a9ef099..c829df8c 100644 --- a/src/com/jfoenix/skins/JFXCheckBoxOldSkin.java +++ b/jfoenix/src/main/java/com/jfoenix/skins/JFXCheckBoxOldSkin.java @@ -1,229 +1,229 @@ -/* - * 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.skins; - -import com.jfoenix.controls.JFXCheckBox; -import com.jfoenix.controls.JFXRippler; -import com.jfoenix.controls.JFXRippler.RipplerMask; -import com.jfoenix.transitions.CachedTransition; -import com.sun.javafx.scene.control.skin.CheckBoxSkin; -import javafx.animation.*; -import javafx.geometry.HPos; -import javafx.geometry.Insets; -import javafx.geometry.VPos; -import javafx.scene.control.CheckBox; -import javafx.scene.layout.*; -import javafx.scene.shape.Line; -import javafx.util.Duration; - -/** - *

Material Design CheckBox Skin

- * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXCheckBoxOldSkin extends CheckBoxSkin { - - private final StackPane box = new StackPane(); - private double lineThick = 2; - private double padding = 10; - private double boxWidth; - private double maxHeight; - private double boxHeight; - private final JFXRippler rippler; - - private Line rightLine; - private Line leftLine; - - private final AnchorPane container = new AnchorPane(); - private double labelOffset = 0; - - private Transition transition; - - private boolean invalid = true; - - public JFXCheckBoxOldSkin(JFXCheckBox control) { - super(control); - - box.setMinSize(20, 20); - box.setPrefSize(20, 20); - box.setMaxSize(20, 20); - box.setBorder(new Border(new BorderStroke(control.getUnCheckedColor(),BorderStrokeStyle.SOLID,new CornerRadii(0), new BorderWidths(lineThick)))); - // - StackPane boxContainer = new StackPane(); - boxContainer.getChildren().add(box); - boxContainer.setPadding(new Insets(padding)); - rippler = new JFXRippler(boxContainer,RipplerMask.CIRCLE); - rippler.setRipplerFill(getSkinnable().isSelected()?control.getUnCheckedColor():control.getCheckedColor()); - - rightLine = new Line(); - leftLine = new Line(); - rightLine.setStroke(control.getCheckedColor()); - rightLine.setStrokeWidth(lineThick); - leftLine.setStroke(control.getCheckedColor()); - leftLine.setStrokeWidth(lineThick); - rightLine.setVisible(false); - leftLine.setVisible(false); - - container.getChildren().add(rightLine); - container.getChildren().add(leftLine); - container.getChildren().add(rippler); - AnchorPane.setRightAnchor(rippler, labelOffset); - - // add listeners - getSkinnable().selectedProperty().addListener((o,oldVal,newVal) ->{ - rippler.setRipplerFill(newVal?control.getUnCheckedColor():control.getCheckedColor()); - transition.setRate(newVal?1:-1); - transition.play(); - }); - - updateChildren(); - - } - - @Override protected void updateChildren() { - super.updateChildren(); - if (container != null) { - getChildren().remove(1); - getChildren().add(container); - } - } - - @Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { - return super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset) + snapSize(box.minWidth(-1))+labelOffset+2*padding; - } - - @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { - return super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset) + snapSize(box.prefWidth(-1))+labelOffset+2*padding; - } - - @Override - protected void layoutChildren(final double x, final double y, final double w, final double h) { - - final CheckBox checkBox = getSkinnable(); - boxWidth = snapSize(container.prefWidth(-1)); - boxHeight = snapSize(container.prefHeight(-1)); - final double computeWidth = Math.min(checkBox.prefWidth(-1),checkBox.minWidth(-1))+labelOffset+2*padding; - final double labelWidth = Math.min( computeWidth - boxWidth, w - snapSize(boxWidth))+labelOffset+2*padding; - final double labelHeight = Math.min(checkBox.prefHeight(labelWidth), h); - maxHeight = Math.max(boxHeight, labelHeight); - final double xOffset = computeXOffset(w, labelWidth + boxWidth, checkBox.getAlignment().getHpos()) + x; - final double yOffset = computeYOffset(h, maxHeight, checkBox.getAlignment().getVpos()) + x; - - if(invalid){ - rightLine.setStartX((boxWidth+padding-labelOffset)/2 - boxWidth/5.5 ); - rightLine.setStartY(maxHeight-padding-lineThick); - rightLine.setEndX((boxWidth+padding-labelOffset)/2 - boxWidth/5.5 ); - rightLine.setEndY( maxHeight-padding-lineThick); - leftLine.setStartX((boxWidth+padding-labelOffset)/2 - boxWidth/5.5 ); - leftLine.setStartY(maxHeight-padding-lineThick); - leftLine.setEndX((boxWidth+padding-labelOffset)/2 - boxWidth/5.5 ); - leftLine.setEndY(maxHeight-padding-lineThick); - transition = new CheckBoxTransition(); - if(getSkinnable().isSelected()) - transition.play(); - invalid = false; - } - - layoutLabelInArea(xOffset + boxWidth, yOffset, labelWidth, maxHeight, checkBox.getAlignment()); - container.resize(boxWidth, boxHeight); - positionInArea(container, xOffset, yOffset, boxWidth, maxHeight, 0, checkBox.getAlignment().getHpos(), checkBox.getAlignment().getVpos()); - - } - - - static double computeXOffset(double width, double contentWidth, HPos hpos) { - switch(hpos) { - case LEFT: - return 0; - case CENTER: - return (width - contentWidth) / 2; - case RIGHT: - return width - contentWidth; - } - return 0; - } - - static double computeYOffset(double height, double contentHeight, VPos vpos) { - - switch(vpos) { - case TOP: - return 0; - case CENTER: - return (height - contentHeight) / 2; - case BOTTOM: - return height - contentHeight; - default: - return 0; - } - } - - private class CheckBoxTransition extends CachedTransition { - - public CheckBoxTransition() { - super(box, new Timeline( - new KeyFrame( - Duration.ZERO, - new KeyValue(rightLine.visibleProperty(), false,Interpolator.EASE_BOTH), - new KeyValue(leftLine.visibleProperty(), false,Interpolator.EASE_BOTH), - new KeyValue(box.rotateProperty(), 0 ,Interpolator.EASE_BOTH), - new KeyValue(box.scaleXProperty(), 1 ,Interpolator.EASE_BOTH), - new KeyValue(box.scaleYProperty(), 1 ,Interpolator.EASE_BOTH), - new KeyValue(box.translateYProperty(), 0 ,Interpolator.EASE_BOTH), - new KeyValue(box.translateXProperty(), 0 ,Interpolator.EASE_BOTH), - new KeyValue(box.opacityProperty(), 1 ,Interpolator.EASE_BOTH) - ), - new KeyFrame(Duration.millis(400), - new KeyValue(rightLine.visibleProperty(), true,Interpolator.EASE_BOTH), - new KeyValue(leftLine.visibleProperty(), true,Interpolator.EASE_BOTH), - new KeyValue(rightLine.endXProperty(), (boxWidth+padding-labelOffset)/2 - boxWidth/5.5 ,Interpolator.EASE_BOTH), - new KeyValue(rightLine.endYProperty(), maxHeight-padding-2*lineThick ,Interpolator.EASE_BOTH), - new KeyValue(leftLine.endXProperty(), (boxWidth+padding-labelOffset)/2 - boxWidth/5.5 ,Interpolator.EASE_BOTH), - new KeyValue(leftLine.endYProperty(), maxHeight-padding-2*lineThick ,Interpolator.EASE_BOTH) - ), - new KeyFrame(Duration.millis(500), - new KeyValue(box.rotateProperty(), 44 ,Interpolator.EASE_BOTH), - new KeyValue(box.scaleXProperty(), 0.3 ,Interpolator.EASE_BOTH), - new KeyValue(box.scaleYProperty(), 0.4 ,Interpolator.EASE_BOTH), - new KeyValue(box.translateYProperty(), boxHeight/12 ,Interpolator.EASE_BOTH), - new KeyValue(box.translateXProperty(), - boxWidth/12 ,Interpolator.EASE_BOTH) - ), - new KeyFrame(Duration.millis(700), - new KeyValue(box.opacityProperty(), 0 ,Interpolator.EASE_BOTH) - ), - new KeyFrame( - Duration.millis(800), - new KeyValue(rightLine.endXProperty(), boxWidth-padding-labelOffset + lineThick/2 ,Interpolator.EASE_BOTH), - new KeyValue(rightLine.endYProperty(), (maxHeight-padding)/2.4 ,Interpolator.EASE_BOTH), - new KeyValue(leftLine.endXProperty(), padding + lineThick/4 ,Interpolator.EASE_BOTH), - new KeyValue(leftLine.endYProperty(), (maxHeight-padding)/1.4 ,Interpolator.EASE_BOTH) - ) - - ) - ); - // reduce the number to increase the shifting , increase number to reduce shifting - setCycleDuration(Duration.seconds(0.4)); - setDelay(Duration.seconds(0)); - } - - } - - -} +/* + * 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.skins; + +import com.jfoenix.controls.JFXCheckBox; +import com.jfoenix.controls.JFXRippler; +import com.jfoenix.controls.JFXRippler.RipplerMask; +import com.jfoenix.transitions.CachedTransition; +import com.sun.javafx.scene.control.skin.CheckBoxSkin; +import javafx.animation.*; +import javafx.geometry.HPos; +import javafx.geometry.Insets; +import javafx.geometry.VPos; +import javafx.scene.control.CheckBox; +import javafx.scene.layout.*; +import javafx.scene.shape.Line; +import javafx.util.Duration; + +/** + *

Material Design CheckBox Skin

+ * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXCheckBoxOldSkin extends CheckBoxSkin { + + private final StackPane box = new StackPane(); + private double lineThick = 2; + private double padding = 10; + private double boxWidth; + private double maxHeight; + private double boxHeight; + private final JFXRippler rippler; + + private Line rightLine; + private Line leftLine; + + private final AnchorPane container = new AnchorPane(); + private double labelOffset = 0; + + private Transition transition; + + private boolean invalid = true; + + public JFXCheckBoxOldSkin(JFXCheckBox control) { + super(control); + + box.setMinSize(20, 20); + box.setPrefSize(20, 20); + box.setMaxSize(20, 20); + box.setBorder(new Border(new BorderStroke(control.getUnCheckedColor(),BorderStrokeStyle.SOLID,new CornerRadii(0), new BorderWidths(lineThick)))); + // + StackPane boxContainer = new StackPane(); + boxContainer.getChildren().add(box); + boxContainer.setPadding(new Insets(padding)); + rippler = new JFXRippler(boxContainer,RipplerMask.CIRCLE); + rippler.setRipplerFill(getSkinnable().isSelected()?control.getUnCheckedColor():control.getCheckedColor()); + + rightLine = new Line(); + leftLine = new Line(); + rightLine.setStroke(control.getCheckedColor()); + rightLine.setStrokeWidth(lineThick); + leftLine.setStroke(control.getCheckedColor()); + leftLine.setStrokeWidth(lineThick); + rightLine.setVisible(false); + leftLine.setVisible(false); + + container.getChildren().add(rightLine); + container.getChildren().add(leftLine); + container.getChildren().add(rippler); + AnchorPane.setRightAnchor(rippler, labelOffset); + + // add listeners + getSkinnable().selectedProperty().addListener((o,oldVal,newVal) ->{ + rippler.setRipplerFill(newVal?control.getUnCheckedColor():control.getCheckedColor()); + transition.setRate(newVal?1:-1); + transition.play(); + }); + + updateChildren(); + + } + + @Override protected void updateChildren() { + super.updateChildren(); + if (container != null) { + getChildren().remove(1); + getChildren().add(container); + } + } + + @Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { + return super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset) + snapSize(box.minWidth(-1))+labelOffset+2*padding; + } + + @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { + return super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset) + snapSize(box.prefWidth(-1))+labelOffset+2*padding; + } + + @Override + protected void layoutChildren(final double x, final double y, final double w, final double h) { + + final CheckBox checkBox = getSkinnable(); + boxWidth = snapSize(container.prefWidth(-1)); + boxHeight = snapSize(container.prefHeight(-1)); + final double computeWidth = Math.min(checkBox.prefWidth(-1),checkBox.minWidth(-1))+labelOffset+2*padding; + final double labelWidth = Math.min( computeWidth - boxWidth, w - snapSize(boxWidth))+labelOffset+2*padding; + final double labelHeight = Math.min(checkBox.prefHeight(labelWidth), h); + maxHeight = Math.max(boxHeight, labelHeight); + final double xOffset = computeXOffset(w, labelWidth + boxWidth, checkBox.getAlignment().getHpos()) + x; + final double yOffset = computeYOffset(h, maxHeight, checkBox.getAlignment().getVpos()) + x; + + if(invalid){ + rightLine.setStartX((boxWidth+padding-labelOffset)/2 - boxWidth/5.5 ); + rightLine.setStartY(maxHeight-padding-lineThick); + rightLine.setEndX((boxWidth+padding-labelOffset)/2 - boxWidth/5.5 ); + rightLine.setEndY( maxHeight-padding-lineThick); + leftLine.setStartX((boxWidth+padding-labelOffset)/2 - boxWidth/5.5 ); + leftLine.setStartY(maxHeight-padding-lineThick); + leftLine.setEndX((boxWidth+padding-labelOffset)/2 - boxWidth/5.5 ); + leftLine.setEndY(maxHeight-padding-lineThick); + transition = new CheckBoxTransition(); + if(getSkinnable().isSelected()) + transition.play(); + invalid = false; + } + + layoutLabelInArea(xOffset + boxWidth, yOffset, labelWidth, maxHeight, checkBox.getAlignment()); + container.resize(boxWidth, boxHeight); + positionInArea(container, xOffset, yOffset, boxWidth, maxHeight, 0, checkBox.getAlignment().getHpos(), checkBox.getAlignment().getVpos()); + + } + + + static double computeXOffset(double width, double contentWidth, HPos hpos) { + switch(hpos) { + case LEFT: + return 0; + case CENTER: + return (width - contentWidth) / 2; + case RIGHT: + return width - contentWidth; + } + return 0; + } + + static double computeYOffset(double height, double contentHeight, VPos vpos) { + + switch(vpos) { + case TOP: + return 0; + case CENTER: + return (height - contentHeight) / 2; + case BOTTOM: + return height - contentHeight; + default: + return 0; + } + } + + private class CheckBoxTransition extends CachedTransition { + + public CheckBoxTransition() { + super(box, new Timeline( + new KeyFrame( + Duration.ZERO, + new KeyValue(rightLine.visibleProperty(), false,Interpolator.EASE_BOTH), + new KeyValue(leftLine.visibleProperty(), false,Interpolator.EASE_BOTH), + new KeyValue(box.rotateProperty(), 0 ,Interpolator.EASE_BOTH), + new KeyValue(box.scaleXProperty(), 1 ,Interpolator.EASE_BOTH), + new KeyValue(box.scaleYProperty(), 1 ,Interpolator.EASE_BOTH), + new KeyValue(box.translateYProperty(), 0 ,Interpolator.EASE_BOTH), + new KeyValue(box.translateXProperty(), 0 ,Interpolator.EASE_BOTH), + new KeyValue(box.opacityProperty(), 1 ,Interpolator.EASE_BOTH) + ), + new KeyFrame(Duration.millis(400), + new KeyValue(rightLine.visibleProperty(), true,Interpolator.EASE_BOTH), + new KeyValue(leftLine.visibleProperty(), true,Interpolator.EASE_BOTH), + new KeyValue(rightLine.endXProperty(), (boxWidth+padding-labelOffset)/2 - boxWidth/5.5 ,Interpolator.EASE_BOTH), + new KeyValue(rightLine.endYProperty(), maxHeight-padding-2*lineThick ,Interpolator.EASE_BOTH), + new KeyValue(leftLine.endXProperty(), (boxWidth+padding-labelOffset)/2 - boxWidth/5.5 ,Interpolator.EASE_BOTH), + new KeyValue(leftLine.endYProperty(), maxHeight-padding-2*lineThick ,Interpolator.EASE_BOTH) + ), + new KeyFrame(Duration.millis(500), + new KeyValue(box.rotateProperty(), 44 ,Interpolator.EASE_BOTH), + new KeyValue(box.scaleXProperty(), 0.3 ,Interpolator.EASE_BOTH), + new KeyValue(box.scaleYProperty(), 0.4 ,Interpolator.EASE_BOTH), + new KeyValue(box.translateYProperty(), boxHeight/12 ,Interpolator.EASE_BOTH), + new KeyValue(box.translateXProperty(), - boxWidth/12 ,Interpolator.EASE_BOTH) + ), + new KeyFrame(Duration.millis(700), + new KeyValue(box.opacityProperty(), 0 ,Interpolator.EASE_BOTH) + ), + new KeyFrame( + Duration.millis(800), + new KeyValue(rightLine.endXProperty(), boxWidth-padding-labelOffset + lineThick/2 ,Interpolator.EASE_BOTH), + new KeyValue(rightLine.endYProperty(), (maxHeight-padding)/2.4 ,Interpolator.EASE_BOTH), + new KeyValue(leftLine.endXProperty(), padding + lineThick/4 ,Interpolator.EASE_BOTH), + new KeyValue(leftLine.endYProperty(), (maxHeight-padding)/1.4 ,Interpolator.EASE_BOTH) + ) + + ) + ); + // reduce the number to increase the shifting , increase number to reduce shifting + setCycleDuration(Duration.seconds(0.4)); + setDelay(Duration.seconds(0)); + } + + } + + +} diff --git a/src/com/jfoenix/skins/JFXCheckBoxSkin.java b/jfoenix/src/main/java/com/jfoenix/skins/JFXCheckBoxSkin.java similarity index 97% rename from src/com/jfoenix/skins/JFXCheckBoxSkin.java rename to jfoenix/src/main/java/com/jfoenix/skins/JFXCheckBoxSkin.java index a2b8a0e5..47872eae 100644 --- a/src/com/jfoenix/skins/JFXCheckBoxSkin.java +++ b/jfoenix/src/main/java/com/jfoenix/skins/JFXCheckBoxSkin.java @@ -1,268 +1,268 @@ -/* - * 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.skins; - -import com.jfoenix.controls.JFXCheckBox; -import com.jfoenix.controls.JFXRippler; -import com.jfoenix.controls.JFXRippler.RipplerMask; -import com.jfoenix.transitions.CachedTransition; -import com.jfoenix.transitions.JFXFillTransition; -import com.sun.javafx.scene.control.skin.CheckBoxSkin; -import javafx.animation.*; -import javafx.geometry.HPos; -import javafx.geometry.Insets; -import javafx.geometry.VPos; -import javafx.scene.control.CheckBox; -import javafx.scene.layout.*; -import javafx.scene.paint.Color; -import javafx.scene.shape.SVGPath; -import javafx.util.Duration; - -/** - *

Material Design CheckBox Skin v1.1

- * the old skin is still supported using {@link JFXCheckBoxOldSkin} - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-09-06 - */ -public class JFXCheckBoxSkin extends CheckBoxSkin { - - private final StackPane box = new StackPane(); - private final StackPane mark = new StackPane(); - private double lineThick = 2; - private double padding = 10; - private double boxWidth; - private double maxHeight; - private double boxHeight; - private final JFXRippler rippler; - - - private final AnchorPane container = new AnchorPane(); - private double labelOffset = -8; - - private Transition transition; - - private boolean invalid = true; - private JFXFillTransition select; - - public JFXCheckBoxSkin(JFXCheckBox control) { - super(control); - - box.setMinSize(18, 18); - box.setPrefSize(18, 18); - box.setMaxSize(18, 18); - box.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, new CornerRadii(2), Insets.EMPTY))); - box.setBorder(new Border(new BorderStroke(control.getUnCheckedColor(),BorderStrokeStyle.SOLID,new CornerRadii(2), new BorderWidths(lineThick)))); - // - StackPane boxContainer = new StackPane(); - boxContainer.getChildren().add(box); - boxContainer.setPadding(new Insets(padding)); - rippler = new JFXRippler(boxContainer,RipplerMask.CIRCLE, JFXRippler.RipplerPos.BACK); - updateRippleColor(); - - SVGPath shape = new SVGPath(); - shape.setContent("M384 690l452-452 60 60-512 512-238-238 60-60z"); - mark.setShape(shape); - mark.setMaxSize(15, 12); - mark.setStyle("-fx-background-color:WHITE; -fx-border-color:WHITE; -fx-border-width:2px;"); - mark.setVisible(false); - mark.setScaleX(0); - mark.setScaleY(0); - boxContainer.getChildren().add(mark); - - container.getChildren().add(rippler); - AnchorPane.setRightAnchor(rippler, labelOffset); - - // add listeners - control.selectedProperty().addListener((o,oldVal,newVal) ->{ - updateRippleColor(); - playSelectAnimation(newVal); - }); - - // show focused state - control.focusedProperty().addListener((o,oldVal,newVal)->{ - if(newVal){ - if(!getSkinnable().isPressed()) rippler.showOverlay(); - }else rippler.hideOverlay(); - }); - control.pressedProperty().addListener((o,oldVal,newVal)-> rippler.hideOverlay()); - - - updateChildren(); - - registerChangeListener(control.checkedColorProperty(), "CHECKED_COLOR"); - } - - private void updateRippleColor() { - rippler.setRipplerFill(getSkinnable().isSelected() ? ((JFXCheckBox)getSkinnable()).getCheckedColor() : ((JFXCheckBox)getSkinnable()).getUnCheckedColor()); - } - - @Override - protected void handleControlPropertyChanged(String p) { - super.handleControlPropertyChanged(p); - if("CHECKED_COLOR".equals(p)){ - createFillTransition(); - } - } - - @Override protected void updateChildren() { - super.updateChildren(); - if (container != null) { - getChildren().remove(1); - getChildren().add(container); - } - } - - @Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { - return super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset) + snapSize(box.minWidth(-1))+labelOffset+2*padding; - } - - @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { - return super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset) + snapSize(box.prefWidth(-1))+labelOffset+2*padding; - } - - @Override - protected void layoutChildren(final double x, final double y, final double w, final double h) { - - final CheckBox checkBox = getSkinnable(); - boxWidth = snapSize(container.prefWidth(-1)); - boxHeight = snapSize(container.prefHeight(-1)); - final double computeWidth = Math.min(checkBox.prefWidth(-1),checkBox.minWidth(-1))+labelOffset+2*padding; - final double labelWidth = Math.min( computeWidth - boxWidth, w - snapSize(boxWidth))+labelOffset+2*padding; - final double labelHeight = Math.min(checkBox.prefHeight(labelWidth), h); - maxHeight = Math.max(boxHeight, labelHeight); - final double xOffset = computeXOffset(w, labelWidth + boxWidth, checkBox.getAlignment().getHpos()) + x; - final double yOffset = computeYOffset(h, maxHeight, checkBox.getAlignment().getVpos()) + x; - - if(invalid){ - transition = new CheckBoxTransition(); - createFillTransition(); - if(getSkinnable().isSelected()){ - playSelectAnimation(true); - } - invalid = false; - } - - layoutLabelInArea(xOffset + boxWidth, yOffset, labelWidth, maxHeight, checkBox.getAlignment()); - container.resize(boxWidth, boxHeight); - positionInArea(container, xOffset, yOffset, boxWidth, maxHeight, 0, checkBox.getAlignment().getHpos(), checkBox.getAlignment().getVpos()); - - } - - - static double computeXOffset(double width, double contentWidth, HPos hpos) { - switch(hpos) { - case LEFT: - return 0; - case CENTER: - return (width - contentWidth) / 2; - case RIGHT: - return width - contentWidth; - } - return 0; - } - - static double computeYOffset(double height, double contentHeight, VPos vpos) { - - switch(vpos) { - case TOP: - return 0; - case CENTER: - return (height - contentHeight) / 2; - case BOTTOM: - return height - contentHeight; - default: - return 0; - } - } - - private void playSelectAnimation(Boolean selection) { - if(selection == null) selection = false; - JFXCheckBox control = ((JFXCheckBox) getSkinnable()); - transition.setRate(selection?1:-1); - select.setRate(selection?1:-1); - transition.play(); - select.play(); - box.setBorder(new Border(new BorderStroke(selection?control.getCheckedColor():control.getUnCheckedColor(),BorderStrokeStyle.SOLID,new CornerRadii(2), new BorderWidths(lineThick)))); - } - - private void createFillTransition(){ - select = new JFXFillTransition(Duration.millis(120), box, Color.TRANSPARENT, (Color)((JFXCheckBox)getSkinnable()).getCheckedColor()); - select.setInterpolator(Interpolator.EASE_OUT); - } - - private class CheckBoxTransition extends CachedTransition { - - public CheckBoxTransition() { - super(mark, new Timeline( - new KeyFrame( - Duration.ZERO, - // new KeyValue(rightLine.visibleProperty(), false,Interpolator.EASE_BOTH), - new KeyValue(mark.visibleProperty(), false,Interpolator.EASE_BOTH), - new KeyValue(mark.scaleXProperty(), 0.5,Interpolator.EASE_OUT), - new KeyValue(mark.scaleYProperty(), 0.5,Interpolator.EASE_OUT) - // new KeyValue(box.rotateProperty(), 0 ,Interpolator.EASE_BOTH), - // new KeyValue(box.scaleXProperty(), 1 ,Interpolator.EASE_BOTH), - // new KeyValue(box.scaleYProperty(), 1 ,Interpolator.EASE_BOTH), - // new KeyValue(box.translateYProperty(), 0 ,Interpolator.EASE_BOTH), - // new KeyValue(box.translateXProperty(), 0 ,Interpolator.EASE_BOTH), - // new KeyValue(box.opacityProperty(), 1 ,Interpolator.EASE_BOTH) - ), - new KeyFrame(Duration.millis(400), - new KeyValue(mark.visibleProperty(), true,Interpolator.EASE_OUT), - new KeyValue(mark.scaleXProperty(), 0.5,Interpolator.EASE_OUT), - new KeyValue(mark.scaleYProperty(), 0.5,Interpolator.EASE_OUT) - // new KeyValue(leftLine.visibleProperty(), true,Interpolator.EASE_BOTH), - // new KeyValue(rightLine.endXProperty(), (boxWidth+padding-labelOffset)/2 - boxWidth/5.5 ,Interpolator.EASE_BOTH), - // new KeyValue(rightLine.endYProperty(), maxHeight-padding-2*lineThick ,Interpolator.EASE_BOTH), - // new KeyValue(leftLine.endXProperty(), (boxWidth+padding-labelOffset)/2 - boxWidth/5.5 ,Interpolator.EASE_BOTH), - // new KeyValue(leftLine.endYProperty(), maxHeight-padding-2*lineThick ,Interpolator.EASE_BOTH) - ), - // new KeyFrame(Duration.millis(500), - // new KeyValue(box.rotateProperty(), 44 ,Interpolator.EASE_BOTH), - // new KeyValue(box.scaleXProperty(), 0.3 ,Interpolator.EASE_BOTH), - // new KeyValue(box.scaleYProperty(), 0.4 ,Interpolator.EASE_BOTH), - // new KeyValue(box.translateYProperty(), boxHeight/12 ,Interpolator.EASE_BOTH), - // new KeyValue(box.translateXProperty(), - boxWidth/12 ,Interpolator.EASE_BOTH) - // ), - // new KeyFrame(Duration.millis(700), - // new KeyValue(box.opacityProperty(), 0 ,Interpolator.EASE_BOTH) - // ), - new KeyFrame( - Duration.millis(1000), - new KeyValue(mark.scaleXProperty(), 1,Interpolator.EASE_OUT), - new KeyValue(mark.scaleYProperty(), 1,Interpolator.EASE_OUT) - // new KeyValue(rightLine.endXProperty(), boxWidth-padding-labelOffset + lineThick/2 ,Interpolator.EASE_BOTH), - // new KeyValue(rightLine.endYProperty(), (maxHeight-padding)/2.4 ,Interpolator.EASE_BOTH), - // new KeyValue(leftLine.endXProperty(), padding + lineThick/4 ,Interpolator.EASE_BOTH), - // new KeyValue(leftLine.endYProperty(), (maxHeight-padding)/1.4 ,Interpolator.EASE_BOTH) - ) - - ) - ); - // reduce the number to increase the shifting , increase number to reduce shifting - setCycleDuration(Duration.seconds(0.12)); - setDelay(Duration.seconds(0.05)); - } - - } - - -} +/* + * 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.skins; + +import com.jfoenix.controls.JFXCheckBox; +import com.jfoenix.controls.JFXRippler; +import com.jfoenix.controls.JFXRippler.RipplerMask; +import com.jfoenix.transitions.CachedTransition; +import com.jfoenix.transitions.JFXFillTransition; +import com.sun.javafx.scene.control.skin.CheckBoxSkin; +import javafx.animation.*; +import javafx.geometry.HPos; +import javafx.geometry.Insets; +import javafx.geometry.VPos; +import javafx.scene.control.CheckBox; +import javafx.scene.layout.*; +import javafx.scene.paint.Color; +import javafx.scene.shape.SVGPath; +import javafx.util.Duration; + +/** + *

Material Design CheckBox Skin v1.1

+ * the old skin is still supported using {@link JFXCheckBoxOldSkin} + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-09-06 + */ +public class JFXCheckBoxSkin extends CheckBoxSkin { + + private final StackPane box = new StackPane(); + private final StackPane mark = new StackPane(); + private double lineThick = 2; + private double padding = 10; + private double boxWidth; + private double maxHeight; + private double boxHeight; + private final JFXRippler rippler; + + + private final AnchorPane container = new AnchorPane(); + private double labelOffset = -8; + + private Transition transition; + + private boolean invalid = true; + private JFXFillTransition select; + + public JFXCheckBoxSkin(JFXCheckBox control) { + super(control); + + box.setMinSize(18, 18); + box.setPrefSize(18, 18); + box.setMaxSize(18, 18); + box.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, new CornerRadii(2), Insets.EMPTY))); + box.setBorder(new Border(new BorderStroke(control.getUnCheckedColor(),BorderStrokeStyle.SOLID,new CornerRadii(2), new BorderWidths(lineThick)))); + // + StackPane boxContainer = new StackPane(); + boxContainer.getChildren().add(box); + boxContainer.setPadding(new Insets(padding)); + rippler = new JFXRippler(boxContainer,RipplerMask.CIRCLE, JFXRippler.RipplerPos.BACK); + updateRippleColor(); + + SVGPath shape = new SVGPath(); + shape.setContent("M384 690l452-452 60 60-512 512-238-238 60-60z"); + mark.setShape(shape); + mark.setMaxSize(15, 12); + mark.setStyle("-fx-background-color:WHITE; -fx-border-color:WHITE; -fx-border-width:2px;"); + mark.setVisible(false); + mark.setScaleX(0); + mark.setScaleY(0); + boxContainer.getChildren().add(mark); + + container.getChildren().add(rippler); + AnchorPane.setRightAnchor(rippler, labelOffset); + + // add listeners + control.selectedProperty().addListener((o,oldVal,newVal) ->{ + updateRippleColor(); + playSelectAnimation(newVal); + }); + + // show focused state + control.focusedProperty().addListener((o,oldVal,newVal)->{ + if(newVal){ + if(!getSkinnable().isPressed()) rippler.showOverlay(); + }else rippler.hideOverlay(); + }); + control.pressedProperty().addListener((o,oldVal,newVal)-> rippler.hideOverlay()); + + + updateChildren(); + + registerChangeListener(control.checkedColorProperty(), "CHECKED_COLOR"); + } + + private void updateRippleColor() { + rippler.setRipplerFill(getSkinnable().isSelected() ? ((JFXCheckBox)getSkinnable()).getCheckedColor() : ((JFXCheckBox)getSkinnable()).getUnCheckedColor()); + } + + @Override + protected void handleControlPropertyChanged(String p) { + super.handleControlPropertyChanged(p); + if("CHECKED_COLOR".equals(p)){ + createFillTransition(); + } + } + + @Override protected void updateChildren() { + super.updateChildren(); + if (container != null) { + getChildren().remove(1); + getChildren().add(container); + } + } + + @Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { + return super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset) + snapSize(box.minWidth(-1))+labelOffset+2*padding; + } + + @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { + return super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset) + snapSize(box.prefWidth(-1))+labelOffset+2*padding; + } + + @Override + protected void layoutChildren(final double x, final double y, final double w, final double h) { + + final CheckBox checkBox = getSkinnable(); + boxWidth = snapSize(container.prefWidth(-1)); + boxHeight = snapSize(container.prefHeight(-1)); + final double computeWidth = Math.min(checkBox.prefWidth(-1),checkBox.minWidth(-1))+labelOffset+2*padding; + final double labelWidth = Math.min( computeWidth - boxWidth, w - snapSize(boxWidth))+labelOffset+2*padding; + final double labelHeight = Math.min(checkBox.prefHeight(labelWidth), h); + maxHeight = Math.max(boxHeight, labelHeight); + final double xOffset = computeXOffset(w, labelWidth + boxWidth, checkBox.getAlignment().getHpos()) + x; + final double yOffset = computeYOffset(h, maxHeight, checkBox.getAlignment().getVpos()) + x; + + if(invalid){ + transition = new CheckBoxTransition(); + createFillTransition(); + if(getSkinnable().isSelected()){ + playSelectAnimation(true); + } + invalid = false; + } + + layoutLabelInArea(xOffset + boxWidth, yOffset, labelWidth, maxHeight, checkBox.getAlignment()); + container.resize(boxWidth, boxHeight); + positionInArea(container, xOffset, yOffset, boxWidth, maxHeight, 0, checkBox.getAlignment().getHpos(), checkBox.getAlignment().getVpos()); + + } + + + static double computeXOffset(double width, double contentWidth, HPos hpos) { + switch(hpos) { + case LEFT: + return 0; + case CENTER: + return (width - contentWidth) / 2; + case RIGHT: + return width - contentWidth; + } + return 0; + } + + static double computeYOffset(double height, double contentHeight, VPos vpos) { + + switch(vpos) { + case TOP: + return 0; + case CENTER: + return (height - contentHeight) / 2; + case BOTTOM: + return height - contentHeight; + default: + return 0; + } + } + + private void playSelectAnimation(Boolean selection) { + if(selection == null) selection = false; + JFXCheckBox control = ((JFXCheckBox) getSkinnable()); + transition.setRate(selection?1:-1); + select.setRate(selection?1:-1); + transition.play(); + select.play(); + box.setBorder(new Border(new BorderStroke(selection?control.getCheckedColor():control.getUnCheckedColor(),BorderStrokeStyle.SOLID,new CornerRadii(2), new BorderWidths(lineThick)))); + } + + private void createFillTransition(){ + select = new JFXFillTransition(Duration.millis(120), box, Color.TRANSPARENT, (Color)((JFXCheckBox)getSkinnable()).getCheckedColor()); + select.setInterpolator(Interpolator.EASE_OUT); + } + + private class CheckBoxTransition extends CachedTransition { + + public CheckBoxTransition() { + super(mark, new Timeline( + new KeyFrame( + Duration.ZERO, + // new KeyValue(rightLine.visibleProperty(), false,Interpolator.EASE_BOTH), + new KeyValue(mark.visibleProperty(), false,Interpolator.EASE_BOTH), + new KeyValue(mark.scaleXProperty(), 0.5,Interpolator.EASE_OUT), + new KeyValue(mark.scaleYProperty(), 0.5,Interpolator.EASE_OUT) + // new KeyValue(box.rotateProperty(), 0 ,Interpolator.EASE_BOTH), + // new KeyValue(box.scaleXProperty(), 1 ,Interpolator.EASE_BOTH), + // new KeyValue(box.scaleYProperty(), 1 ,Interpolator.EASE_BOTH), + // new KeyValue(box.translateYProperty(), 0 ,Interpolator.EASE_BOTH), + // new KeyValue(box.translateXProperty(), 0 ,Interpolator.EASE_BOTH), + // new KeyValue(box.opacityProperty(), 1 ,Interpolator.EASE_BOTH) + ), + new KeyFrame(Duration.millis(400), + new KeyValue(mark.visibleProperty(), true,Interpolator.EASE_OUT), + new KeyValue(mark.scaleXProperty(), 0.5,Interpolator.EASE_OUT), + new KeyValue(mark.scaleYProperty(), 0.5,Interpolator.EASE_OUT) + // new KeyValue(leftLine.visibleProperty(), true,Interpolator.EASE_BOTH), + // new KeyValue(rightLine.endXProperty(), (boxWidth+padding-labelOffset)/2 - boxWidth/5.5 ,Interpolator.EASE_BOTH), + // new KeyValue(rightLine.endYProperty(), maxHeight-padding-2*lineThick ,Interpolator.EASE_BOTH), + // new KeyValue(leftLine.endXProperty(), (boxWidth+padding-labelOffset)/2 - boxWidth/5.5 ,Interpolator.EASE_BOTH), + // new KeyValue(leftLine.endYProperty(), maxHeight-padding-2*lineThick ,Interpolator.EASE_BOTH) + ), + // new KeyFrame(Duration.millis(500), + // new KeyValue(box.rotateProperty(), 44 ,Interpolator.EASE_BOTH), + // new KeyValue(box.scaleXProperty(), 0.3 ,Interpolator.EASE_BOTH), + // new KeyValue(box.scaleYProperty(), 0.4 ,Interpolator.EASE_BOTH), + // new KeyValue(box.translateYProperty(), boxHeight/12 ,Interpolator.EASE_BOTH), + // new KeyValue(box.translateXProperty(), - boxWidth/12 ,Interpolator.EASE_BOTH) + // ), + // new KeyFrame(Duration.millis(700), + // new KeyValue(box.opacityProperty(), 0 ,Interpolator.EASE_BOTH) + // ), + new KeyFrame( + Duration.millis(1000), + new KeyValue(mark.scaleXProperty(), 1,Interpolator.EASE_OUT), + new KeyValue(mark.scaleYProperty(), 1,Interpolator.EASE_OUT) + // new KeyValue(rightLine.endXProperty(), boxWidth-padding-labelOffset + lineThick/2 ,Interpolator.EASE_BOTH), + // new KeyValue(rightLine.endYProperty(), (maxHeight-padding)/2.4 ,Interpolator.EASE_BOTH), + // new KeyValue(leftLine.endXProperty(), padding + lineThick/4 ,Interpolator.EASE_BOTH), + // new KeyValue(leftLine.endYProperty(), (maxHeight-padding)/1.4 ,Interpolator.EASE_BOTH) + ) + + ) + ); + // reduce the number to increase the shifting , increase number to reduce shifting + setCycleDuration(Duration.seconds(0.12)); + setDelay(Duration.seconds(0.05)); + } + + } + + +} diff --git a/src/com/jfoenix/skins/JFXColorPalette.java b/jfoenix/src/main/java/com/jfoenix/skins/JFXColorPalette.java similarity index 96% rename from src/com/jfoenix/skins/JFXColorPalette.java rename to jfoenix/src/main/java/com/jfoenix/skins/JFXColorPalette.java index cf4586cf..bd572392 100644 --- a/src/com/jfoenix/skins/JFXColorPalette.java +++ b/jfoenix/src/main/java/com/jfoenix/skins/JFXColorPalette.java @@ -1,629 +1,629 @@ -/* - * 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.skins; - -import com.jfoenix.controls.JFXButton; -import javafx.application.Platform; -import javafx.collections.FXCollections; -import javafx.collections.ListChangeListener.Change; -import javafx.collections.ObservableList; -import javafx.event.ActionEvent; -import javafx.event.Event; -import javafx.event.EventHandler; -import javafx.geometry.Bounds; -import javafx.geometry.Insets; -import javafx.geometry.NodeOrientation; -import javafx.geometry.Pos; -import javafx.scene.Node; -import javafx.scene.control.ColorPicker; -import javafx.scene.control.Label; -import javafx.scene.control.PopupControl; -import javafx.scene.control.Tooltip; -import javafx.scene.input.KeyEvent; -import javafx.scene.input.MouseButton; -import javafx.scene.input.MouseEvent; -import javafx.scene.layout.*; -import javafx.scene.paint.Color; -import javafx.scene.shape.Rectangle; -import javafx.scene.shape.StrokeType; - -import java.util.List; - -/** - * @author Shadi Shaheen - * FUTURE WORK: this UI will get re-designed to match material design guidlines - */ - -class JFXColorPalette extends Region { - - private static final int SQUARE_SIZE = 15; - - // package protected for testing purposes - JFXColorGrid colorPickerGrid; - final JFXButton customColorLink = new JFXButton("Custom Color"); - JFXCustomColorPickerDialog customColorDialog = null; - - private ColorPicker colorPicker; - private final GridPane customColorGrid = new GridPane(); - private final Label customColorLabel = new Label("Recent Colors"); - - private PopupControl popupControl; - private ColorSquare focusedSquare; - - private Color mouseDragColor = null; - private boolean dragDetected = false; - - - private final ColorSquare hoverSquare = new ColorSquare(); - - public JFXColorPalette(final ColorPicker colorPicker) { - getStyleClass().add("color-palette-region"); - this.colorPicker = colorPicker; - colorPickerGrid = new JFXColorGrid(); - colorPickerGrid.getChildren().get(0).requestFocus(); - customColorLabel.setAlignment(Pos.CENTER_LEFT); - customColorLink.setPrefWidth(colorPickerGrid.prefWidth(-1)); - customColorLink.setAlignment(Pos.CENTER); - customColorLink.setFocusTraversable(true); - customColorLink.setOnAction(new EventHandler() { - @Override public void handle(ActionEvent t) { - if (customColorDialog == null) { - customColorDialog = new JFXCustomColorPickerDialog(popupControl); - customColorDialog.customColorProperty().addListener((ov, t1, t2) -> { - colorPicker.setValue(customColorDialog.customColorProperty().get()); - }); - customColorDialog.setOnSave(() -> { - Color customColor = customColorDialog.customColorProperty().get(); - buildCustomColors(); - colorPicker.getCustomColors().add(customColor); - updateSelection(customColor); - Event.fireEvent(colorPicker, new ActionEvent()); - colorPicker.hide(); - }); - } - customColorDialog.setCurrentColor(colorPicker.valueProperty().get()); - if (popupControl != null) popupControl.setAutoHide(false); - customColorDialog.show(); - customColorDialog.setOnHidden(event -> { - if (popupControl != null) popupControl.setAutoHide(true); - }); - } - }); - - initNavigation(); - customColorGrid.getStyleClass().add("color-picker-grid"); - customColorGrid.setVisible(false); - - buildCustomColors(); - - colorPicker.getCustomColors().addListener((Change change) -> buildCustomColors()); - VBox paletteBox = new VBox(); - paletteBox.getStyleClass().add("color-palette"); - paletteBox.setBackground(new Background(new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY))); - paletteBox.setBorder(new Border(new BorderStroke(Color.valueOf("#9E9E9E"), BorderStrokeStyle.SOLID, CornerRadii.EMPTY, BorderWidths.DEFAULT))); - paletteBox.getChildren().addAll(colorPickerGrid, customColorLabel, customColorGrid, customColorLink); - - hoverSquare.setMouseTransparent(true); - hoverSquare.getStyleClass().addAll("hover-square"); - setFocusedSquare(null); - - getChildren().addAll(paletteBox, hoverSquare); - Platform.runLater(()->{ - customColorDialog = new JFXCustomColorPickerDialog(popupControl); - customColorDialog.customColorProperty().addListener((ov, t1, t2) -> { - colorPicker.setValue(customColorDialog.customColorProperty().get()); - }); - customColorDialog.setOnSave(() -> { - Color customColor = customColorDialog.customColorProperty().get(); - buildCustomColors(); - colorPicker.getCustomColors().add(customColor); - updateSelection(customColor); - Event.fireEvent(colorPicker, new ActionEvent()); - colorPicker.hide(); - }); - }); - } - - private void setFocusedSquare(ColorSquare square) { - hoverSquare.setVisible(square != null); - - if (square == focusedSquare) { - return; - } - focusedSquare = square; - - hoverSquare.setVisible(focusedSquare != null); - if (focusedSquare == null) { - return; - } - - if (!focusedSquare.isFocused()) { - focusedSquare.requestFocus(); - } - - hoverSquare.rectangle.setFill(focusedSquare.rectangle.getFill()); - - Bounds b = square.localToScene(square.getLayoutBounds()); - - double x = b.getMinX(); - double y = b.getMinY(); - - double xAdjust; - double scaleAdjust = hoverSquare.getScaleX() == 1.0 ? 0 : hoverSquare.getWidth() / 4.0; - - if (colorPicker.getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT) { - x = focusedSquare.getLayoutX(); - xAdjust = -focusedSquare.getWidth() + scaleAdjust; - } else { - xAdjust = focusedSquare.getWidth() / 2.0 + scaleAdjust; - } - - hoverSquare.setLayoutX(snapPosition(x) - xAdjust); - hoverSquare.setLayoutY(snapPosition(y) - focusedSquare.getHeight() / 2.0 + (hoverSquare.getScaleY() == 1.0 ? 0 : focusedSquare.getHeight() / 4.0)); - } - - private void buildCustomColors() { - final ObservableList customColors = colorPicker.getCustomColors(); - customColorGrid.getChildren().clear(); - if (customColors.isEmpty()) { - customColorLabel.setVisible(false); - customColorLabel.setManaged(false); - customColorGrid.setVisible(false); - customColorGrid.setManaged(false); - return; - } else { - customColorLabel.setVisible(true); - customColorLabel.setManaged(true); - customColorGrid.setVisible(true); - customColorGrid.setManaged(true); - } - - int customColumnIndex = 0; - int customRowIndex = 0; - int remainingSquares = customColors.size() % NUM_OF_COLUMNS; - int numEmpty = (remainingSquares == 0) ? 0 : NUM_OF_COLUMNS - remainingSquares; - - for (int i = 0; i < customColors.size(); i++) { - Color c = customColors.get(i); - ColorSquare square = new ColorSquare(c, i, true); - customColorGrid.add(square, customColumnIndex, customRowIndex); - customColumnIndex++; - if (customColumnIndex == NUM_OF_COLUMNS) { - customColumnIndex = 0; - customRowIndex++; - } - } - for (int i = 0; i < numEmpty; i++) { - ColorSquare emptySquare = new ColorSquare(); - customColorGrid.add(emptySquare, customColumnIndex, customRowIndex); - customColumnIndex++; - } - requestLayout(); - } - - private void initNavigation() { - setOnKeyPressed(ke -> { - switch (ke.getCode()) { - case SPACE: - case ENTER: - // select the focused color - if (focusedSquare != null) focusedSquare.selectColor(ke); - ke.consume(); - break; - default: // no-op - } - }); - } - - public void setPopupControl(PopupControl pc) { - this.popupControl = pc; - } - - public JFXColorGrid getColorGrid() { - return colorPickerGrid; - } - - public boolean isCustomColorDialogShowing() { - if (customColorDialog != null) return customColorDialog.isVisible(); - return false; - } - - class ColorSquare extends StackPane { - Rectangle rectangle; -// int index; - boolean isEmpty; -// boolean isCustom; - - public ColorSquare() { - this(null, -1, false); - } - - public ColorSquare(Color color, int index) { - this(color, index, false); - } - - public ColorSquare(Color color, int index, boolean isCustom) { - // Add style class to handle selected color square - getStyleClass().add("color-square"); - if (color != null) { - setFocusTraversable(true); - focusedProperty().addListener((s, ov, nv) -> setFocusedSquare(nv ? this : null)); - addEventHandler(MouseEvent.MOUSE_ENTERED, event -> setFocusedSquare(ColorSquare.this)); - addEventHandler(MouseEvent.MOUSE_EXITED, event -> setFocusedSquare(null)); - addEventHandler(MouseEvent.MOUSE_RELEASED, event -> { - if (!dragDetected && event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 1) { - if (!isEmpty) { - Color fill = (Color) rectangle.getFill(); - colorPicker.setValue(fill); - colorPicker.fireEvent(new ActionEvent()); - updateSelection(fill); - event.consume(); - } - colorPicker.hide(); - } - }); - } -// this.index = index; -// this.isCustom = isCustom; - rectangle = new Rectangle(SQUARE_SIZE, SQUARE_SIZE); - if (color == null) { - rectangle.setFill(Color.WHITE); - isEmpty = true; - } else { - rectangle.setFill(color); - } - - rectangle.setStrokeType(StrokeType.INSIDE); - - String tooltipStr = JFXColorPickerSkin.tooltipString(color); - Tooltip.install(this, new Tooltip((tooltipStr == null) ? "".toUpperCase() : tooltipStr.toUpperCase())); - - rectangle.getStyleClass().add("color-rect"); - getChildren().add(rectangle); - } - - public void selectColor(KeyEvent event) { - if (rectangle.getFill() != null) { - if (rectangle.getFill() instanceof Color) { - colorPicker.setValue((Color) rectangle.getFill()); - colorPicker.fireEvent(new ActionEvent()); - } - event.consume(); - } - colorPicker.hide(); - } - } - - // The skin can update selection if colorpicker value changes.. - public void updateSelection(Color color) { - setFocusedSquare(null); - - for (ColorSquare c : colorPickerGrid.getSquares()) { - if (c.rectangle.getFill().equals(color)) { - setFocusedSquare(c); - return; - } - } - // check custom colors - for (Node n : customColorGrid.getChildren()) { - ColorSquare c = (ColorSquare) n; - if (c.rectangle.getFill().equals(color)) { - setFocusedSquare(c); - return; - } - } - } - - class JFXColorGrid extends GridPane { - - private final List squares; - - public JFXColorGrid() { - getStyleClass().add("color-picker-grid"); - setId("ColorCustomizerColorGrid"); - int columnIndex = 0, rowIndex = 0; - squares = FXCollections.observableArrayList(); - final int numColors = RAW_VALUES.length / 3; - Color[] colors = new Color[numColors]; - for (int i = 0; i < numColors; i++) { - colors[i] = new Color(RAW_VALUES[(i * 3)] / 255, - RAW_VALUES[(i * 3) + 1] / 255, RAW_VALUES[(i * 3) + 2] / 255, - 1.0); - ColorSquare cs = new ColorSquare(colors[i], i); - squares.add(cs); - } - - for (ColorSquare square : squares) { - add(square, columnIndex, rowIndex); - columnIndex++; - if (columnIndex == NUM_OF_COLUMNS) { - columnIndex = 0; - rowIndex++; - } - } - setOnMouseDragged(t -> { - if (!dragDetected) { - dragDetected = true; - mouseDragColor = colorPicker.getValue(); - } - int xIndex = clamp(0, - (int)t.getX()/(SQUARE_SIZE + 1), NUM_OF_COLUMNS - 1); - int yIndex = clamp(0, - (int)t.getY()/(SQUARE_SIZE + 1), NUM_OF_ROWS - 1); - int index = xIndex + yIndex*NUM_OF_COLUMNS; - colorPicker.setValue((Color) squares.get(index).rectangle.getFill()); - updateSelection(colorPicker.getValue()); - }); - addEventHandler(MouseEvent.MOUSE_RELEASED, t -> { - if(colorPickerGrid.getBoundsInLocal().contains(t.getX(), t.getY())) { - updateSelection(colorPicker.getValue()); - colorPicker.fireEvent(new ActionEvent()); - colorPicker.hide(); - } else { - // restore color as mouse release happened outside the grid. - if (mouseDragColor != null) { - colorPicker.setValue(mouseDragColor); - updateSelection(mouseDragColor); - } - } - dragDetected = false; - }); - } - - public List getSquares() { - return squares; - } - - @Override protected double computePrefWidth(double height) { - return (SQUARE_SIZE + 1)*NUM_OF_COLUMNS; - } - - @Override protected double computePrefHeight(double width) { - return (SQUARE_SIZE + 1)*NUM_OF_ROWS; - } - } - - private static final int NUM_OF_COLUMNS = 10; - private static double[] RAW_VALUES = { - // WARNING: always make sure the number of colors is a divisable by NUM_OF_COLUMNS - 250, 250, 250, // first row - 245, 245, 245, - 238, 238, 238, - 224, 224, 224, - 189, 189, 189, - 158, 158, 158, - 117, 117, 117, - 97, 97, 97, - 66, 66, 66, - 33, 33, 33, - // second row - 236, 239, 241, - 207, 216, 220, - 176, 190, 197, - 144, 164, 174, - 120, 144, 156, - 96, 125, 139, - 84, 110, 122, - 69, 90, 100, - 55, 71, 79, - 38, 50, 56, - // third row - 255,235,238, - 255,205,210, - 239,154,154, - 229,115,115, - 239,83,80, - 244,67,54, - 229,57,53, - 211,47,47, - 198,40,40, - 183,28,28, - // forth row - 252,228,236, - 248,187,208, - 244,143,177, - 240,98,146, - 236,64,122, - 233,30,99, - 216,27,96, - 194,24,91, - 173,20,87, - 136,14,79, - // fifth row - 243,229,245, - 225,190,231, - 206,147,216, - 186,104,200, - 171,71,188, - 156,39,176, - 142,36,170, - 123,31,162, - 106,27,154, - 74,20,140, - // sixth row - 237,231,246, - 209,196,233, - 179,157,219, - 149,117,205, - 126,87,194, - 103,58,183, - 94,53,177, - 81,45,168, - 69,39,160, - 49,27,146, - // seventh row - 232,234,246, - 197,202,233, - 159,168,218, - 121,134,203, - 92,107,192, - 63,81,181, - 57,73,171, - 48,63,159, - 40,53,147, - 26,35,126, - // eigth row - 227,242,253, - 187,222,251, - 144,202,249, - 100,181,246, - 66,165,245, - 33,150,243, - 30,136,229, - 25,118,210, - 21,101,192, - 13,71,161, - // ninth row - 225,245,254, - 179,229,252, - 129,212,250, - 79,195,247, - 41,182,246, - 3,169,244, - 3,155,229, - 2,136,209, - 2,119,189, - 1,87,155, - // tenth row - 224,247,250, - 178,235,242, - 128,222,234, - 77,208,225, - 38,198,218, - 0,188,212, - 0,172,193, - 0,151,167, - 0,131,143, - 0,96,100, - // eleventh row - 224,242,241, - 178,223,219, - 128,203,196, - 77,182,172, - 38,166,154, - 0,150,136, - 0,137,123, - 0,121,107, - 0,105,92, - 0,77,64, - // twelfth row - 232,245,233, - 200,230,201, - 165,214,167, - 129,199,132, - 102,187,106, - 76,175,80, - 67,160,71, - 56,142,60, - 46,125,50, - 27,94,32, - - // thirteenth row - 241,248,233, - 220,237,200, - 197,225,165, - 174,213,129, - 156,204,101, - 139,195,74, - 124,179,66, - 104,159,56, - 85,139,47, - 51,105,30, - // fourteenth row - 249,251,231, - 240,244,195, - 230,238,156, - 220,231,117, - 212,225,87, - 205,220,57, - 192,202,51, - 175,180,43, - 158,157,36, - 130,119,23, - - // fifteenth row - 255,253,231, - 255,249,196, - 255,245,157, - 255,241,118, - 255,238,88, - 255,235,59, - 253,216,53, - 251,192,45, - 249,168,37, - 245,127,23, - - // sixteenth row - 255,248,225, - 255,236,179, - 255,224,130, - 255,213,79, - 255,202,40, - 255,193,7, - 255,179,0, - 255,160,0, - 255,143,0, - 255,111,0, - - // seventeenth row - 255,243,224, - 255,224,178, - 255,204,128, - 255,183,77, - 255,167,38, - 255,152,0, - 251,140,0, - 245,124,0, - 239,108,0, - 230,81,0, - - // eighteenth row - 251,233,231, - 255,204,188, - 255,171,145, - 255,138,101, - 255,112,67, - 255,87,34, - 244,81,30, - 230,74,25, - 216,67,21, - 191,54,12, - - // nineteenth row - 239,235,233, - 215,204,200, - 188,170,164, - 161,136,127, - 141,110,99, - 121,85,72, - 109,76,65, - 93,64,55, - 78,52,46, - 62,39,35, - }; - - private static final int NUM_OF_COLORS = RAW_VALUES.length / 3; - private static final int NUM_OF_ROWS = NUM_OF_COLORS / NUM_OF_COLUMNS; - - private static int clamp(int min, int value, int max) { - if (value < min) return min; - if (value > max) return max; - return value; - } +/* + * 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.skins; + +import com.jfoenix.controls.JFXButton; +import javafx.application.Platform; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener.Change; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.event.Event; +import javafx.event.EventHandler; +import javafx.geometry.Bounds; +import javafx.geometry.Insets; +import javafx.geometry.NodeOrientation; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.ColorPicker; +import javafx.scene.control.Label; +import javafx.scene.control.PopupControl; +import javafx.scene.control.Tooltip; +import javafx.scene.input.KeyEvent; +import javafx.scene.input.MouseButton; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.*; +import javafx.scene.paint.Color; +import javafx.scene.shape.Rectangle; +import javafx.scene.shape.StrokeType; + +import java.util.List; + +/** + * @author Shadi Shaheen + * FUTURE WORK: this UI will get re-designed to match material design guidlines + */ + +class JFXColorPalette extends Region { + + private static final int SQUARE_SIZE = 15; + + // package protected for testing purposes + JFXColorGrid colorPickerGrid; + final JFXButton customColorLink = new JFXButton("Custom Color"); + JFXCustomColorPickerDialog customColorDialog = null; + + private ColorPicker colorPicker; + private final GridPane customColorGrid = new GridPane(); + private final Label customColorLabel = new Label("Recent Colors"); + + private PopupControl popupControl; + private ColorSquare focusedSquare; + + private Color mouseDragColor = null; + private boolean dragDetected = false; + + + private final ColorSquare hoverSquare = new ColorSquare(); + + public JFXColorPalette(final ColorPicker colorPicker) { + getStyleClass().add("color-palette-region"); + this.colorPicker = colorPicker; + colorPickerGrid = new JFXColorGrid(); + colorPickerGrid.getChildren().get(0).requestFocus(); + customColorLabel.setAlignment(Pos.CENTER_LEFT); + customColorLink.setPrefWidth(colorPickerGrid.prefWidth(-1)); + customColorLink.setAlignment(Pos.CENTER); + customColorLink.setFocusTraversable(true); + customColorLink.setOnAction(new EventHandler() { + @Override public void handle(ActionEvent t) { + if (customColorDialog == null) { + customColorDialog = new JFXCustomColorPickerDialog(popupControl); + customColorDialog.customColorProperty().addListener((ov, t1, t2) -> { + colorPicker.setValue(customColorDialog.customColorProperty().get()); + }); + customColorDialog.setOnSave(() -> { + Color customColor = customColorDialog.customColorProperty().get(); + buildCustomColors(); + colorPicker.getCustomColors().add(customColor); + updateSelection(customColor); + Event.fireEvent(colorPicker, new ActionEvent()); + colorPicker.hide(); + }); + } + customColorDialog.setCurrentColor(colorPicker.valueProperty().get()); + if (popupControl != null) popupControl.setAutoHide(false); + customColorDialog.show(); + customColorDialog.setOnHidden(event -> { + if (popupControl != null) popupControl.setAutoHide(true); + }); + } + }); + + initNavigation(); + customColorGrid.getStyleClass().add("color-picker-grid"); + customColorGrid.setVisible(false); + + buildCustomColors(); + + colorPicker.getCustomColors().addListener((Change change) -> buildCustomColors()); + VBox paletteBox = new VBox(); + paletteBox.getStyleClass().add("color-palette"); + paletteBox.setBackground(new Background(new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY))); + paletteBox.setBorder(new Border(new BorderStroke(Color.valueOf("#9E9E9E"), BorderStrokeStyle.SOLID, CornerRadii.EMPTY, BorderWidths.DEFAULT))); + paletteBox.getChildren().addAll(colorPickerGrid, customColorLabel, customColorGrid, customColorLink); + + hoverSquare.setMouseTransparent(true); + hoverSquare.getStyleClass().addAll("hover-square"); + setFocusedSquare(null); + + getChildren().addAll(paletteBox, hoverSquare); + Platform.runLater(()->{ + customColorDialog = new JFXCustomColorPickerDialog(popupControl); + customColorDialog.customColorProperty().addListener((ov, t1, t2) -> { + colorPicker.setValue(customColorDialog.customColorProperty().get()); + }); + customColorDialog.setOnSave(() -> { + Color customColor = customColorDialog.customColorProperty().get(); + buildCustomColors(); + colorPicker.getCustomColors().add(customColor); + updateSelection(customColor); + Event.fireEvent(colorPicker, new ActionEvent()); + colorPicker.hide(); + }); + }); + } + + private void setFocusedSquare(ColorSquare square) { + hoverSquare.setVisible(square != null); + + if (square == focusedSquare) { + return; + } + focusedSquare = square; + + hoverSquare.setVisible(focusedSquare != null); + if (focusedSquare == null) { + return; + } + + if (!focusedSquare.isFocused()) { + focusedSquare.requestFocus(); + } + + hoverSquare.rectangle.setFill(focusedSquare.rectangle.getFill()); + + Bounds b = square.localToScene(square.getLayoutBounds()); + + double x = b.getMinX(); + double y = b.getMinY(); + + double xAdjust; + double scaleAdjust = hoverSquare.getScaleX() == 1.0 ? 0 : hoverSquare.getWidth() / 4.0; + + if (colorPicker.getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT) { + x = focusedSquare.getLayoutX(); + xAdjust = -focusedSquare.getWidth() + scaleAdjust; + } else { + xAdjust = focusedSquare.getWidth() / 2.0 + scaleAdjust; + } + + hoverSquare.setLayoutX(snapPosition(x) - xAdjust); + hoverSquare.setLayoutY(snapPosition(y) - focusedSquare.getHeight() / 2.0 + (hoverSquare.getScaleY() == 1.0 ? 0 : focusedSquare.getHeight() / 4.0)); + } + + private void buildCustomColors() { + final ObservableList customColors = colorPicker.getCustomColors(); + customColorGrid.getChildren().clear(); + if (customColors.isEmpty()) { + customColorLabel.setVisible(false); + customColorLabel.setManaged(false); + customColorGrid.setVisible(false); + customColorGrid.setManaged(false); + return; + } else { + customColorLabel.setVisible(true); + customColorLabel.setManaged(true); + customColorGrid.setVisible(true); + customColorGrid.setManaged(true); + } + + int customColumnIndex = 0; + int customRowIndex = 0; + int remainingSquares = customColors.size() % NUM_OF_COLUMNS; + int numEmpty = (remainingSquares == 0) ? 0 : NUM_OF_COLUMNS - remainingSquares; + + for (int i = 0; i < customColors.size(); i++) { + Color c = customColors.get(i); + ColorSquare square = new ColorSquare(c, i, true); + customColorGrid.add(square, customColumnIndex, customRowIndex); + customColumnIndex++; + if (customColumnIndex == NUM_OF_COLUMNS) { + customColumnIndex = 0; + customRowIndex++; + } + } + for (int i = 0; i < numEmpty; i++) { + ColorSquare emptySquare = new ColorSquare(); + customColorGrid.add(emptySquare, customColumnIndex, customRowIndex); + customColumnIndex++; + } + requestLayout(); + } + + private void initNavigation() { + setOnKeyPressed(ke -> { + switch (ke.getCode()) { + case SPACE: + case ENTER: + // select the focused color + if (focusedSquare != null) focusedSquare.selectColor(ke); + ke.consume(); + break; + default: // no-op + } + }); + } + + public void setPopupControl(PopupControl pc) { + this.popupControl = pc; + } + + public JFXColorGrid getColorGrid() { + return colorPickerGrid; + } + + public boolean isCustomColorDialogShowing() { + if (customColorDialog != null) return customColorDialog.isVisible(); + return false; + } + + class ColorSquare extends StackPane { + Rectangle rectangle; +// int index; + boolean isEmpty; +// boolean isCustom; + + public ColorSquare() { + this(null, -1, false); + } + + public ColorSquare(Color color, int index) { + this(color, index, false); + } + + public ColorSquare(Color color, int index, boolean isCustom) { + // Add style class to handle selected color square + getStyleClass().add("color-square"); + if (color != null) { + setFocusTraversable(true); + focusedProperty().addListener((s, ov, nv) -> setFocusedSquare(nv ? this : null)); + addEventHandler(MouseEvent.MOUSE_ENTERED, event -> setFocusedSquare(ColorSquare.this)); + addEventHandler(MouseEvent.MOUSE_EXITED, event -> setFocusedSquare(null)); + addEventHandler(MouseEvent.MOUSE_RELEASED, event -> { + if (!dragDetected && event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 1) { + if (!isEmpty) { + Color fill = (Color) rectangle.getFill(); + colorPicker.setValue(fill); + colorPicker.fireEvent(new ActionEvent()); + updateSelection(fill); + event.consume(); + } + colorPicker.hide(); + } + }); + } +// this.index = index; +// this.isCustom = isCustom; + rectangle = new Rectangle(SQUARE_SIZE, SQUARE_SIZE); + if (color == null) { + rectangle.setFill(Color.WHITE); + isEmpty = true; + } else { + rectangle.setFill(color); + } + + rectangle.setStrokeType(StrokeType.INSIDE); + + String tooltipStr = JFXColorPickerSkin.tooltipString(color); + Tooltip.install(this, new Tooltip((tooltipStr == null) ? "".toUpperCase() : tooltipStr.toUpperCase())); + + rectangle.getStyleClass().add("color-rect"); + getChildren().add(rectangle); + } + + public void selectColor(KeyEvent event) { + if (rectangle.getFill() != null) { + if (rectangle.getFill() instanceof Color) { + colorPicker.setValue((Color) rectangle.getFill()); + colorPicker.fireEvent(new ActionEvent()); + } + event.consume(); + } + colorPicker.hide(); + } + } + + // The skin can update selection if colorpicker value changes.. + public void updateSelection(Color color) { + setFocusedSquare(null); + + for (ColorSquare c : colorPickerGrid.getSquares()) { + if (c.rectangle.getFill().equals(color)) { + setFocusedSquare(c); + return; + } + } + // check custom colors + for (Node n : customColorGrid.getChildren()) { + ColorSquare c = (ColorSquare) n; + if (c.rectangle.getFill().equals(color)) { + setFocusedSquare(c); + return; + } + } + } + + class JFXColorGrid extends GridPane { + + private final List squares; + + public JFXColorGrid() { + getStyleClass().add("color-picker-grid"); + setId("ColorCustomizerColorGrid"); + int columnIndex = 0, rowIndex = 0; + squares = FXCollections.observableArrayList(); + final int numColors = RAW_VALUES.length / 3; + Color[] colors = new Color[numColors]; + for (int i = 0; i < numColors; i++) { + colors[i] = new Color(RAW_VALUES[(i * 3)] / 255, + RAW_VALUES[(i * 3) + 1] / 255, RAW_VALUES[(i * 3) + 2] / 255, + 1.0); + ColorSquare cs = new ColorSquare(colors[i], i); + squares.add(cs); + } + + for (ColorSquare square : squares) { + add(square, columnIndex, rowIndex); + columnIndex++; + if (columnIndex == NUM_OF_COLUMNS) { + columnIndex = 0; + rowIndex++; + } + } + setOnMouseDragged(t -> { + if (!dragDetected) { + dragDetected = true; + mouseDragColor = colorPicker.getValue(); + } + int xIndex = clamp(0, + (int)t.getX()/(SQUARE_SIZE + 1), NUM_OF_COLUMNS - 1); + int yIndex = clamp(0, + (int)t.getY()/(SQUARE_SIZE + 1), NUM_OF_ROWS - 1); + int index = xIndex + yIndex*NUM_OF_COLUMNS; + colorPicker.setValue((Color) squares.get(index).rectangle.getFill()); + updateSelection(colorPicker.getValue()); + }); + addEventHandler(MouseEvent.MOUSE_RELEASED, t -> { + if(colorPickerGrid.getBoundsInLocal().contains(t.getX(), t.getY())) { + updateSelection(colorPicker.getValue()); + colorPicker.fireEvent(new ActionEvent()); + colorPicker.hide(); + } else { + // restore color as mouse release happened outside the grid. + if (mouseDragColor != null) { + colorPicker.setValue(mouseDragColor); + updateSelection(mouseDragColor); + } + } + dragDetected = false; + }); + } + + public List getSquares() { + return squares; + } + + @Override protected double computePrefWidth(double height) { + return (SQUARE_SIZE + 1)*NUM_OF_COLUMNS; + } + + @Override protected double computePrefHeight(double width) { + return (SQUARE_SIZE + 1)*NUM_OF_ROWS; + } + } + + private static final int NUM_OF_COLUMNS = 10; + private static double[] RAW_VALUES = { + // WARNING: always make sure the number of colors is a divisable by NUM_OF_COLUMNS + 250, 250, 250, // first row + 245, 245, 245, + 238, 238, 238, + 224, 224, 224, + 189, 189, 189, + 158, 158, 158, + 117, 117, 117, + 97, 97, 97, + 66, 66, 66, + 33, 33, 33, + // second row + 236, 239, 241, + 207, 216, 220, + 176, 190, 197, + 144, 164, 174, + 120, 144, 156, + 96, 125, 139, + 84, 110, 122, + 69, 90, 100, + 55, 71, 79, + 38, 50, 56, + // third row + 255,235,238, + 255,205,210, + 239,154,154, + 229,115,115, + 239,83,80, + 244,67,54, + 229,57,53, + 211,47,47, + 198,40,40, + 183,28,28, + // forth row + 252,228,236, + 248,187,208, + 244,143,177, + 240,98,146, + 236,64,122, + 233,30,99, + 216,27,96, + 194,24,91, + 173,20,87, + 136,14,79, + // fifth row + 243,229,245, + 225,190,231, + 206,147,216, + 186,104,200, + 171,71,188, + 156,39,176, + 142,36,170, + 123,31,162, + 106,27,154, + 74,20,140, + // sixth row + 237,231,246, + 209,196,233, + 179,157,219, + 149,117,205, + 126,87,194, + 103,58,183, + 94,53,177, + 81,45,168, + 69,39,160, + 49,27,146, + // seventh row + 232,234,246, + 197,202,233, + 159,168,218, + 121,134,203, + 92,107,192, + 63,81,181, + 57,73,171, + 48,63,159, + 40,53,147, + 26,35,126, + // eigth row + 227,242,253, + 187,222,251, + 144,202,249, + 100,181,246, + 66,165,245, + 33,150,243, + 30,136,229, + 25,118,210, + 21,101,192, + 13,71,161, + // ninth row + 225,245,254, + 179,229,252, + 129,212,250, + 79,195,247, + 41,182,246, + 3,169,244, + 3,155,229, + 2,136,209, + 2,119,189, + 1,87,155, + // tenth row + 224,247,250, + 178,235,242, + 128,222,234, + 77,208,225, + 38,198,218, + 0,188,212, + 0,172,193, + 0,151,167, + 0,131,143, + 0,96,100, + // eleventh row + 224,242,241, + 178,223,219, + 128,203,196, + 77,182,172, + 38,166,154, + 0,150,136, + 0,137,123, + 0,121,107, + 0,105,92, + 0,77,64, + // twelfth row + 232,245,233, + 200,230,201, + 165,214,167, + 129,199,132, + 102,187,106, + 76,175,80, + 67,160,71, + 56,142,60, + 46,125,50, + 27,94,32, + + // thirteenth row + 241,248,233, + 220,237,200, + 197,225,165, + 174,213,129, + 156,204,101, + 139,195,74, + 124,179,66, + 104,159,56, + 85,139,47, + 51,105,30, + // fourteenth row + 249,251,231, + 240,244,195, + 230,238,156, + 220,231,117, + 212,225,87, + 205,220,57, + 192,202,51, + 175,180,43, + 158,157,36, + 130,119,23, + + // fifteenth row + 255,253,231, + 255,249,196, + 255,245,157, + 255,241,118, + 255,238,88, + 255,235,59, + 253,216,53, + 251,192,45, + 249,168,37, + 245,127,23, + + // sixteenth row + 255,248,225, + 255,236,179, + 255,224,130, + 255,213,79, + 255,202,40, + 255,193,7, + 255,179,0, + 255,160,0, + 255,143,0, + 255,111,0, + + // seventeenth row + 255,243,224, + 255,224,178, + 255,204,128, + 255,183,77, + 255,167,38, + 255,152,0, + 251,140,0, + 245,124,0, + 239,108,0, + 230,81,0, + + // eighteenth row + 251,233,231, + 255,204,188, + 255,171,145, + 255,138,101, + 255,112,67, + 255,87,34, + 244,81,30, + 230,74,25, + 216,67,21, + 191,54,12, + + // nineteenth row + 239,235,233, + 215,204,200, + 188,170,164, + 161,136,127, + 141,110,99, + 121,85,72, + 109,76,65, + 93,64,55, + 78,52,46, + 62,39,35, + }; + + private static final int NUM_OF_COLORS = RAW_VALUES.length / 3; + private static final int NUM_OF_ROWS = NUM_OF_COLORS / NUM_OF_COLUMNS; + + private static int clamp(int min, int value, int max) { + if (value < min) return min; + if (value > max) return max; + return value; + } } \ No newline at end of file diff --git a/src/com/jfoenix/skins/JFXColorPickerSkin.java b/jfoenix/src/main/java/com/jfoenix/skins/JFXColorPickerSkin.java similarity index 97% rename from src/com/jfoenix/skins/JFXColorPickerSkin.java rename to jfoenix/src/main/java/com/jfoenix/skins/JFXColorPickerSkin.java index 84260e80..d136ba4d 100644 --- a/src/com/jfoenix/skins/JFXColorPickerSkin.java +++ b/jfoenix/src/main/java/com/jfoenix/skins/JFXColorPickerSkin.java @@ -1,266 +1,266 @@ -/* - * 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.skins; - -import com.jfoenix.controls.JFXButton; -import com.jfoenix.controls.behavior.JFXColorPickerBehavior; -import com.jfoenix.effects.JFXDepthManager; -import com.sun.javafx.css.converters.BooleanConverter; -import com.sun.javafx.scene.control.skin.ComboBoxBaseSkin; -import com.sun.javafx.scene.control.skin.ComboBoxPopupControl; -import javafx.animation.Interpolator; -import javafx.animation.KeyFrame; -import javafx.animation.KeyValue; -import javafx.animation.Timeline; -import javafx.beans.binding.Bindings; -import javafx.beans.value.WritableValue; -import javafx.css.*; -import javafx.geometry.Insets; -import javafx.scene.Node; -import javafx.scene.control.ColorPicker; -import javafx.scene.control.Label; -import javafx.scene.control.TextField; -import javafx.scene.input.MouseEvent; -import javafx.scene.layout.*; -import javafx.scene.paint.Color; -import javafx.scene.shape.Circle; -import javafx.util.Duration; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Locale; - -/** - * @author Shadi Shaheen - * - */ -public class JFXColorPickerSkin extends ComboBoxPopupControl { - - private Label displayNode; - private Pane pickerColorBox; - private StackPane pickerColorClip; - private JFXColorPalette popupContent; - StyleableBooleanProperty colorLabelVisible = new SimpleStyleableBooleanProperty(StyleableProperties.COLOR_LABEL_VISIBLE,JFXColorPickerSkin.this,"colorLabelVisible",true); - - public JFXColorPickerSkin(final ColorPicker colorPicker) { - - super(colorPicker, new JFXColorPickerBehavior(colorPicker)); - // create displayNode - displayNode = new Label(); - displayNode.getStyleClass().add("color-picker-label"); - displayNode.setManaged(false); - displayNode.setMouseTransparent(true); - - // label graphic - pickerColorBox = new Pane(); - pickerColorBox.getStyleClass().add("picker-color"); - pickerColorBox.setBackground(new Background(new BackgroundFill(Color.valueOf("#fafafa"), new CornerRadii(3), Insets.EMPTY))); - pickerColorClip = new StackPane(); - pickerColorClip.backgroundProperty().bind(Bindings.createObjectBinding(()->{ - return new Background(new BackgroundFill(Color.WHITE, - pickerColorBox.backgroundProperty().get()!=null?pickerColorBox.getBackground().getFills().get(0).getRadii() : new CornerRadii(3), - pickerColorBox.backgroundProperty().get()!=null?pickerColorBox.getBackground().getFills().get(0).getInsets() : Insets.EMPTY)); - }, pickerColorBox.backgroundProperty())); - pickerColorBox.setClip(pickerColorClip); - JFXButton button = new JFXButton(""); - button.ripplerFillProperty().bind(displayNode.textFillProperty()); - button.minWidthProperty().bind(pickerColorBox.widthProperty()); - button.minHeightProperty().bind(pickerColorBox.heightProperty()); - button.addEventHandler(MouseEvent.ANY, (event)->{ - if(!event.isConsumed()){ - event.consume(); - getSkinnable().fireEvent(event); - } - }); - - pickerColorBox.getChildren().add(button); - updateColor(); - getChildren().add(pickerColorBox); - getChildren().remove(arrowButton); - JFXDepthManager.setDepth(getSkinnable(), 1); - // to improve the performance on 1st click - getPopupContent(); - - // add listeners - registerChangeListener(colorPicker.valueProperty(), "VALUE"); - colorLabelVisible.addListener(invalidate->{ - if (displayNode != null) { - if (colorLabelVisible.get()) { - displayNode.setText(colorDisplayName(((ColorPicker)getSkinnable()).getValue())); - } else { - displayNode.setText(""); - } - } - }); - } - - - @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { - if (!colorLabelVisible.get()) { - return super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset); - } - String displayNodeText = displayNode.getText(); - double width = 0; - displayNode.setText("#00000000"); - width = Math.max(width, super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset)); - displayNode.setText(displayNodeText); - return width; - } - - static String colorDisplayName(Color c) { - if (c != null) { - String displayName = formatHexString(c); - return displayName; - } - return null; - } - - static String tooltipString(Color c) { - if (c != null) { - String tooltipStr = formatHexString(c); - return tooltipStr; - } - return null; - } - - static String formatHexString(Color c) { - if (c != null) { - return String.format((Locale) null, "#%02x%02x%02x", - Math.round(c.getRed() * 255), - Math.round(c.getGreen() * 255), - Math.round(c.getBlue() * 255)).toUpperCase(); - } else { - return null; - } - } - - @Override protected Node getPopupContent() { - if (popupContent == null) { - popupContent = new JFXColorPalette((ColorPicker)getSkinnable()); - popupContent.setPopupControl(getPopup()); - } - return popupContent; - } - - @Override protected void focusLost() { } - - @Override public void show() { - super.show(); - final ColorPicker colorPicker = (ColorPicker)getSkinnable(); - popupContent.updateSelection(colorPicker.getValue()); - } - - @Override protected void handleControlPropertyChanged(String p) { - super.handleControlPropertyChanged(p); - if ("SHOWING".equals(p)) { - if (getSkinnable().isShowing()) show(); - else if (!popupContent.isCustomColorDialogShowing()) hide(); - } else if ("VALUE".equals(p)) { - // change the selected color - updateColor(); - } - } - @Override public Node getDisplayNode() { - return displayNode; - } - - private void updateColor() { - final ColorPicker colorPicker = (ColorPicker)getSkinnable(); - // update picker box color - Circle ColorCircle = new Circle(); - ColorCircle.setFill(colorPicker.getValue()); - ColorCircle.setLayoutX(pickerColorBox.getWidth()/4); - ColorCircle.setLayoutY(pickerColorBox.getHeight()/2); - pickerColorBox.getChildren().add(ColorCircle); - Timeline animateColor = new Timeline(new KeyFrame(Duration.millis(240), new KeyValue(ColorCircle.radiusProperty(), 200, Interpolator.EASE_BOTH))); - animateColor.setOnFinished((finish)->{ - pickerColorBox.setBackground(new Background(new BackgroundFill(ColorCircle.getFill(), pickerColorBox.getBackground().getFills().get(0).getRadii(), pickerColorBox.getBackground().getFills().get(0).getInsets()))); - pickerColorBox.getChildren().remove(ColorCircle); - }); - animateColor.play(); - // update label color - displayNode.setTextFill(colorPicker.getValue().grayscale().getRed() < 0.5?Color.valueOf("rgba(255, 255, 255, 0.87)") : Color.valueOf("rgba(0, 0, 0, 0.87)")); - if (colorLabelVisible.get()) displayNode.setText(colorDisplayName(colorPicker.getValue())); - else displayNode.setText(""); - } - public void syncWithAutoUpdate() { - if (!getPopup().isShowing() && getSkinnable().isShowing()) { - // Popup was dismissed. Maybe user clicked outside or typed ESCAPE. - // Make sure JFXColorPickerUI button is in sync. - getSkinnable().hide(); - } - } - - @Override protected void layoutChildren(final double x, final double y, - final double w, final double h) { - pickerColorBox.resizeRelocate(x-1, y-1, w + 2, h + 2); - pickerColorClip.resize(w+2, h+2); - super.layoutChildren(x,y,w,h); - } - - /*************************************************************************** - * * - * Stylesheet Handling * - * * - **************************************************************************/ - - private static class StyleableProperties { - private static final CssMetaData COLOR_LABEL_VISIBLE = - new CssMetaData("-fx-color-label-visible", - BooleanConverter.getInstance(), Boolean.TRUE) { - - @Override public boolean isSettable(ColorPicker n) { - final JFXColorPickerSkin skin = (JFXColorPickerSkin) n.getSkin(); - return skin.colorLabelVisible == null || !skin.colorLabelVisible.isBound(); - } - - @Override public StyleableProperty getStyleableProperty(ColorPicker n) { - final JFXColorPickerSkin skin = (JFXColorPickerSkin) n.getSkin(); - return (StyleableProperty)(WritableValue)skin.colorLabelVisible; - } - }; - private static final List> STYLEABLES; - static { - final List> styleables = - new ArrayList>(ComboBoxBaseSkin.getClassCssMetaData()); - styleables.add(COLOR_LABEL_VISIBLE); - STYLEABLES = Collections.unmodifiableList(styleables); - } - } - - public static List> getClassCssMetaData() { - return StyleableProperties.STYLEABLES; - } - @Override - public List> getCssMetaData() { - return getClassCssMetaData(); - } - - - - protected TextField getEditor() { - return null; - } - - protected javafx.util.StringConverter getConverter() { - return null; - } - -} +/* + * 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.skins; + +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.behavior.JFXColorPickerBehavior; +import com.jfoenix.effects.JFXDepthManager; +import com.sun.javafx.css.converters.BooleanConverter; +import com.sun.javafx.scene.control.skin.ComboBoxBaseSkin; +import com.sun.javafx.scene.control.skin.ComboBoxPopupControl; +import javafx.animation.Interpolator; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.beans.binding.Bindings; +import javafx.beans.value.WritableValue; +import javafx.css.*; +import javafx.geometry.Insets; +import javafx.scene.Node; +import javafx.scene.control.ColorPicker; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.*; +import javafx.scene.paint.Color; +import javafx.scene.shape.Circle; +import javafx.util.Duration; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +/** + * @author Shadi Shaheen + * + */ +public class JFXColorPickerSkin extends ComboBoxPopupControl { + + private Label displayNode; + private Pane pickerColorBox; + private StackPane pickerColorClip; + private JFXColorPalette popupContent; + StyleableBooleanProperty colorLabelVisible = new SimpleStyleableBooleanProperty(StyleableProperties.COLOR_LABEL_VISIBLE,JFXColorPickerSkin.this,"colorLabelVisible",true); + + public JFXColorPickerSkin(final ColorPicker colorPicker) { + + super(colorPicker, new JFXColorPickerBehavior(colorPicker)); + // create displayNode + displayNode = new Label(); + displayNode.getStyleClass().add("color-picker-label"); + displayNode.setManaged(false); + displayNode.setMouseTransparent(true); + + // label graphic + pickerColorBox = new Pane(); + pickerColorBox.getStyleClass().add("picker-color"); + pickerColorBox.setBackground(new Background(new BackgroundFill(Color.valueOf("#fafafa"), new CornerRadii(3), Insets.EMPTY))); + pickerColorClip = new StackPane(); + pickerColorClip.backgroundProperty().bind(Bindings.createObjectBinding(()->{ + return new Background(new BackgroundFill(Color.WHITE, + pickerColorBox.backgroundProperty().get()!=null?pickerColorBox.getBackground().getFills().get(0).getRadii() : new CornerRadii(3), + pickerColorBox.backgroundProperty().get()!=null?pickerColorBox.getBackground().getFills().get(0).getInsets() : Insets.EMPTY)); + }, pickerColorBox.backgroundProperty())); + pickerColorBox.setClip(pickerColorClip); + JFXButton button = new JFXButton(""); + button.ripplerFillProperty().bind(displayNode.textFillProperty()); + button.minWidthProperty().bind(pickerColorBox.widthProperty()); + button.minHeightProperty().bind(pickerColorBox.heightProperty()); + button.addEventHandler(MouseEvent.ANY, (event)->{ + if(!event.isConsumed()){ + event.consume(); + getSkinnable().fireEvent(event); + } + }); + + pickerColorBox.getChildren().add(button); + updateColor(); + getChildren().add(pickerColorBox); + getChildren().remove(arrowButton); + JFXDepthManager.setDepth(getSkinnable(), 1); + // to improve the performance on 1st click + getPopupContent(); + + // add listeners + registerChangeListener(colorPicker.valueProperty(), "VALUE"); + colorLabelVisible.addListener(invalidate->{ + if (displayNode != null) { + if (colorLabelVisible.get()) { + displayNode.setText(colorDisplayName(((ColorPicker)getSkinnable()).getValue())); + } else { + displayNode.setText(""); + } + } + }); + } + + + @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { + if (!colorLabelVisible.get()) { + return super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset); + } + String displayNodeText = displayNode.getText(); + double width = 0; + displayNode.setText("#00000000"); + width = Math.max(width, super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset)); + displayNode.setText(displayNodeText); + return width; + } + + static String colorDisplayName(Color c) { + if (c != null) { + String displayName = formatHexString(c); + return displayName; + } + return null; + } + + static String tooltipString(Color c) { + if (c != null) { + String tooltipStr = formatHexString(c); + return tooltipStr; + } + return null; + } + + static String formatHexString(Color c) { + if (c != null) { + return String.format((Locale) null, "#%02x%02x%02x", + Math.round(c.getRed() * 255), + Math.round(c.getGreen() * 255), + Math.round(c.getBlue() * 255)).toUpperCase(); + } else { + return null; + } + } + + @Override protected Node getPopupContent() { + if (popupContent == null) { + popupContent = new JFXColorPalette((ColorPicker)getSkinnable()); + popupContent.setPopupControl(getPopup()); + } + return popupContent; + } + + @Override protected void focusLost() { } + + @Override public void show() { + super.show(); + final ColorPicker colorPicker = (ColorPicker)getSkinnable(); + popupContent.updateSelection(colorPicker.getValue()); + } + + @Override protected void handleControlPropertyChanged(String p) { + super.handleControlPropertyChanged(p); + if ("SHOWING".equals(p)) { + if (getSkinnable().isShowing()) show(); + else if (!popupContent.isCustomColorDialogShowing()) hide(); + } else if ("VALUE".equals(p)) { + // change the selected color + updateColor(); + } + } + @Override public Node getDisplayNode() { + return displayNode; + } + + private void updateColor() { + final ColorPicker colorPicker = (ColorPicker)getSkinnable(); + // update picker box color + Circle ColorCircle = new Circle(); + ColorCircle.setFill(colorPicker.getValue()); + ColorCircle.setLayoutX(pickerColorBox.getWidth()/4); + ColorCircle.setLayoutY(pickerColorBox.getHeight()/2); + pickerColorBox.getChildren().add(ColorCircle); + Timeline animateColor = new Timeline(new KeyFrame(Duration.millis(240), new KeyValue(ColorCircle.radiusProperty(), 200, Interpolator.EASE_BOTH))); + animateColor.setOnFinished((finish)->{ + pickerColorBox.setBackground(new Background(new BackgroundFill(ColorCircle.getFill(), pickerColorBox.getBackground().getFills().get(0).getRadii(), pickerColorBox.getBackground().getFills().get(0).getInsets()))); + pickerColorBox.getChildren().remove(ColorCircle); + }); + animateColor.play(); + // update label color + displayNode.setTextFill(colorPicker.getValue().grayscale().getRed() < 0.5?Color.valueOf("rgba(255, 255, 255, 0.87)") : Color.valueOf("rgba(0, 0, 0, 0.87)")); + if (colorLabelVisible.get()) displayNode.setText(colorDisplayName(colorPicker.getValue())); + else displayNode.setText(""); + } + public void syncWithAutoUpdate() { + if (!getPopup().isShowing() && getSkinnable().isShowing()) { + // Popup was dismissed. Maybe user clicked outside or typed ESCAPE. + // Make sure JFXColorPickerUI button is in sync. + getSkinnable().hide(); + } + } + + @Override protected void layoutChildren(final double x, final double y, + final double w, final double h) { + pickerColorBox.resizeRelocate(x-1, y-1, w + 2, h + 2); + pickerColorClip.resize(w+2, h+2); + super.layoutChildren(x,y,w,h); + } + + /*************************************************************************** + * * + * Stylesheet Handling * + * * + **************************************************************************/ + + private static class StyleableProperties { + private static final CssMetaData COLOR_LABEL_VISIBLE = + new CssMetaData("-fx-color-label-visible", + BooleanConverter.getInstance(), Boolean.TRUE) { + + @Override public boolean isSettable(ColorPicker n) { + final JFXColorPickerSkin skin = (JFXColorPickerSkin) n.getSkin(); + return skin.colorLabelVisible == null || !skin.colorLabelVisible.isBound(); + } + + @Override public StyleableProperty getStyleableProperty(ColorPicker n) { + final JFXColorPickerSkin skin = (JFXColorPickerSkin) n.getSkin(); + return (StyleableProperty)(WritableValue)skin.colorLabelVisible; + } + }; + private static final List> STYLEABLES; + static { + final List> styleables = + new ArrayList>(ComboBoxBaseSkin.getClassCssMetaData()); + styleables.add(COLOR_LABEL_VISIBLE); + STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + public static List> getClassCssMetaData() { + return StyleableProperties.STYLEABLES; + } + @Override + public List> getCssMetaData() { + return getClassCssMetaData(); + } + + + + protected TextField getEditor() { + return null; + } + + protected javafx.util.StringConverter getConverter() { + return null; + } + +} diff --git a/src/com/jfoenix/skins/JFXColorPickerUI.java b/jfoenix/src/main/java/com/jfoenix/skins/JFXColorPickerUI.java similarity index 97% rename from src/com/jfoenix/skins/JFXColorPickerUI.java rename to jfoenix/src/main/java/com/jfoenix/skins/JFXColorPickerUI.java index d0a47f45..e0d0fd5f 100644 --- a/src/com/jfoenix/skins/JFXColorPickerUI.java +++ b/jfoenix/src/main/java/com/jfoenix/skins/JFXColorPickerUI.java @@ -1,569 +1,569 @@ -/* - * 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.skins; - -import com.jfoenix.effects.JFXDepthManager; -import com.jfoenix.transitions.CachedTransition; -import javafx.animation.Animation.Status; -import javafx.animation.*; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.geometry.Point2D; -import javafx.scene.Node; -import javafx.scene.effect.ColorAdjust; -import javafx.scene.image.Image; -import javafx.scene.image.ImageView; -import javafx.scene.image.PixelWriter; -import javafx.scene.image.WritableImage; -import javafx.scene.input.MouseEvent; -import javafx.scene.layout.Pane; -import javafx.scene.paint.Color; -import javafx.scene.paint.Stop; -import javafx.scene.shape.Circle; -import javafx.scene.shape.Path; -import javafx.util.Duration; - -import java.util.ArrayList; -import java.util.List; - - -/** - * @author Shadi Shaheen & Bassel El Mabsout - * this UI allows the user to pick a color using HSL color system - * - */ -class JFXColorPickerUI extends Pane { - - private CachedTransition selectorTransition; - private int pickerSize = 400; - // sl circle selector size - private int selectorSize = 20; - private double centerX,centerY, pickerRadius, huesRadius, huesSmallR, huesLargeR, slRadius; - double currentHue = 0; - - private ImageView huesCircleView; - private ImageView slCircleView; - private Pane colorSelector; - private Pane selector; - private CurveTransition colorsTransition; - - public JFXColorPickerUI(int pickerSize){ - - JFXDepthManager.setDepth(this, 1); - - this.pickerSize = pickerSize; - this.centerX = (double)pickerSize/2; - this.centerY = (double)pickerSize/2; - this.pickerRadius = (double)pickerSize/2; - this.huesRadius = pickerRadius * 0.9; - this.huesSmallR = pickerRadius * 0.8; - this.huesLargeR = pickerRadius; - this.slRadius = pickerRadius * 0.7; - - // Create Hues Circle - huesCircleView = new ImageView(getHuesCircle(pickerSize,pickerSize,new ArrayList())); - // clip to smooth the edges - Circle outterCircle = new Circle(centerX, centerY, huesLargeR - 2); - Circle innterCircle = new Circle(centerX, centerY, huesSmallR + 2); - huesCircleView.setClip(Path.subtract(outterCircle, innterCircle)); - this.getChildren().add(huesCircleView); - - // create Hues Circle Selector - Circle r1 = new Circle(pickerRadius - huesSmallR); - Circle r2 = new Circle(pickerRadius - huesRadius); - colorSelector = new Pane(); - colorSelector.setStyle("-fx-border-color:#424242; -fx-border-width:1px; -fx-background-color:rgba(255, 255, 255, 0.87);"); - colorSelector.setPrefSize(pickerRadius - huesSmallR,pickerRadius - huesSmallR); - colorSelector.setShape(Path.subtract(r1, r2)); - colorSelector.setCache(true); -// JFXDepthManager.setDepth(colorSelector, 1); - colorSelector.setMouseTransparent(true); - colorSelector.setPickOnBounds(false); - this.getChildren().add(colorSelector); - - // add Hues Selection Listeners - huesCircleView.addEventHandler(MouseEvent.MOUSE_DRAGGED, (event)->{ - if(colorsTransition!=null) colorsTransition.stop(); - double dx = event.getX() - centerX; - double dy = event.getY() - centerY; - double theta = Math.atan2(dy, dx); - double x = centerX + huesRadius * Math.cos(theta); - double y = centerY + huesRadius * Math.sin(theta); - colorSelector.setRotate(90+Math.toDegrees(Math.atan2(dy, dx))); - colorSelector.setTranslateX(x - colorSelector.getPrefWidth()/2); - colorSelector.setTranslateY(y - colorSelector.getPrefHeight()/2); - }); - huesCircleView.addEventHandler(MouseEvent.MOUSE_PRESSED, (event)->{ - double dx = event.getX() - centerX; - double dy = event.getY() - centerY; - double theta = Math.atan2(dy, dx); - double x = centerX + huesRadius * Math.cos(theta); - double y = centerY + huesRadius * Math.sin(theta); - colorsTransition = new CurveTransition(new Point2D(colorSelector.getTranslateX() + colorSelector.getPrefWidth()/2, colorSelector.getTranslateY() + colorSelector.getPrefHeight()/2), new Point2D(x,y)); - colorsTransition.play(); - }); - colorSelector.translateXProperty().addListener((o,oldVal,newVal)-> updateHSLCircleColor((int) (newVal.intValue() + colorSelector.getPrefWidth()/2), (int) (colorSelector.getTranslateY() + colorSelector.getPrefHeight()/2))); - colorSelector.translateYProperty().addListener((o,oldVal,newVal)-> updateHSLCircleColor((int) (colorSelector.getTranslateX() + colorSelector.getPrefWidth()/2), (int) (newVal.intValue() + colorSelector.getPrefHeight()/2))); - - - // Create SL Circle - slCircleView = new ImageView(getSLCricle(pickerSize,pickerSize,new ArrayList())); - slCircleView.setClip(new Circle(centerX,centerY, slRadius - 2)); - slCircleView.setPickOnBounds(false); - this.getChildren().add(slCircleView); - - // create SL Circle Selector - selector = new Pane(); - Circle c1 = new Circle(selectorSize/2); - Circle c2 = new Circle((selectorSize/2) * 0.5); - selector.setShape(Path.subtract(c1, c2)); - selector.setStyle("-fx-border-color:#424242; -fx-border-width:1px;-fx-background-color:rgba(255, 255, 255, 0.87);"); - selector.setPrefSize(selectorSize, selectorSize); - selector.setMinSize(selectorSize, selectorSize); - selector.setMaxSize(selectorSize, selectorSize); -// JFXDepthManager.setDepth(selector, 1); - selector.setCache(true); - selector.setMouseTransparent(true); - this.getChildren().add(selector); - - - // add SL selection Listeners - slCircleView.addEventHandler(MouseEvent.MOUSE_DRAGGED, (event)->{ - if(selectorTransition!=null) selectorTransition.stop(); - if(Math.pow(event.getX()-centerX, 2) + Math.pow( event.getY()-centerY, 2) < Math.pow(slRadius-2, 2)){ - selector.setTranslateX(event.getX()-selector.getPrefWidth()/2); - selector.setTranslateY(event.getY()-selector.getPrefHeight()/2); - }else{ - double dx = event.getX() - centerX; - double dy = event.getY() - centerY; - double theta = Math.atan2(dy, dx); - double x = centerX + (slRadius-2) * Math.cos(theta); - double y = centerY + (slRadius-2) * Math.sin(theta); - selector.setTranslateX(x-selector.getPrefWidth()/2); - selector.setTranslateY(y-selector.getPrefHeight()/2); - } - }); - slCircleView.addEventHandler(MouseEvent.MOUSE_PRESSED, (event)->{ - selectorTransition = new CachedTransition(selector, new Timeline(new KeyFrame(Duration.millis(1000), - new KeyValue(selector.translateXProperty(), event.getX()-selector.getPrefWidth()/2, Interpolator.EASE_BOTH), - new KeyValue(selector.translateYProperty(), event.getY()-selector.getPrefHeight()/2, Interpolator.EASE_BOTH)))){{ - setCycleDuration(Duration.millis(160)); - setDelay(Duration.seconds(0)); - }}; - selectorTransition.play(); - }); - // add slCircleView listener - selector.translateXProperty().addListener((o,oldVal,newVal)-> setColorAtLocation((int)newVal.intValue()+selectorSize/2, (int)selector.getTranslateY()+selectorSize/2)); - selector.translateYProperty().addListener((o,oldVal,newVal)-> setColorAtLocation((int)selector.getTranslateX()+selectorSize/2, (int)newVal.intValue()+selectorSize/2)); - - - - // initial color selection - double dx = 20 - centerX; - double dy = 20 - centerY; - double theta = Math.atan2(dy, dx); - double x = centerX + huesRadius * Math.cos(theta); - double y = centerY + huesRadius * Math.sin(theta); - colorSelector.setRotate(90+Math.toDegrees(Math.atan2(dy, dx))); - colorSelector.setTranslateX(x-colorSelector.getPrefWidth()/2); - colorSelector.setTranslateY(y-colorSelector.getPrefHeight()/2); - selector.setTranslateX(centerX-selector.getPrefWidth()/2); - selector.setTranslateY(centerY-selector.getPrefHeight()/2); - - } - - - /** - * List of Color Nodes that needs to be updated when picking a color - */ - private ObservableList colorNodes = FXCollections.observableArrayList(); - - public void addColorSelectionNode(Node... nodes){ - colorNodes.addAll(nodes); - } - public void removeColorSelectionNode(Node... nodes){ - colorNodes.removeAll(nodes); - } - - private void updateHSLCircleColor(int x, int y) { - // transform color to HSL space - Color color = huesCircleView.getImage().getPixelReader().getColor(x, y); - double max = Math.max(color.getRed(), Math.max(color.getGreen(), color.getBlue())), min = Math.min(color.getRed(), Math.min(color.getGreen(), color.getBlue())); - double hue = 0; - if(max != min){ - double d = max - min; - if(max == color.getRed()){ - hue = (color.getGreen() - color.getBlue()) / d + (color.getGreen() < color.getBlue() ? 6 : 0); - }else if(max == color.getGreen()){ - hue = (color.getBlue() - color.getRed()) / d + 2; - }else if(max == color.getBlue()){ - hue = (color.getRed() - color.getGreen()) / d + 4; - } - hue /= 6; - } - currentHue = map(hue, 0,1, 0,255); - - // refresh the HSL circle - refreshHSLCircle(); - } - - private void refreshHSLCircle() { - ColorAdjust colorAdjust = new ColorAdjust(); - colorAdjust.setHue(map(currentHue + (currentHue<127.5? 1:-1)*127.5, 0,255, -1 , 1)); - slCircleView.setEffect(colorAdjust); - setColorAtLocation((int)selector.getTranslateX()+selectorSize/2, (int)selector.getTranslateY()+selectorSize/2); - } - - - /** - * this method is used to move selectors to a certain color - */ - private boolean allowColorChange = true; - private ParallelTransition pTrans; - - public void moveToColor(Color color){ - allowColorChange = false; - double max = Math.max(color.getRed(), Math.max(color.getGreen(), color.getBlue())), min = Math.min(color.getRed(), Math.min(color.getGreen(), color.getBlue())); - double hue = 0; - double l = (max + min) / 2; - double s = 0; - if(max == min){ - hue = s = 0; // achromatic - } else{ - double d = max - min; - s = l > 0.5 ? d / (2 - max - min) : d / (max + min); - if(max == color.getRed()){ - hue = (color.getGreen() - color.getBlue()) / d + (color.getGreen() < color.getBlue() ? 6 : 0); - }else if(max == color.getGreen()){ - hue = (color.getBlue() - color.getRed()) / d + 2; - }else if(max == color.getBlue()){ - hue = (color.getRed() - color.getGreen()) / d + 4; - } - hue /= 6; - } - currentHue = map(hue, 0,1, 0,255); - - // Animate Hue - double theta = map(currentHue, 0,255, -Math.PI, Math.PI); - double x = centerX + huesRadius * Math.cos(theta); - double y = centerY + huesRadius * Math.sin(theta); - colorsTransition = new CurveTransition(new Point2D(colorSelector.getTranslateX() + colorSelector.getPrefWidth()/2, colorSelector.getTranslateY() + colorSelector.getPrefHeight()/2), new Point2D(x , y )); - - - // Animate SL - s = map(s, 0,1, 0,255); - l = map(l, 0,1, 0,255); - Point2D point = getPointFromSL((int)s, (int)l, slRadius); - double pX = centerX - point.getX(); - double pY = centerY - point.getY(); - - double endPointX; - double endPointY; - if(Math.pow(pX-centerX, 2) + Math.pow(pY-centerY, 2) < Math.pow(slRadius-2,2)){ - endPointX = pX-selector.getPrefWidth()/2 ; - endPointY = pY-selector.getPrefHeight()/2; - }else{ - double dx = pX - centerX; - double dy = pY - centerY; - theta = Math.atan2(dy, dx); - x = centerX + (slRadius-2) * Math.cos(theta); - y = centerY + (slRadius-2) * Math.sin(theta); - endPointX = x-selector.getPrefWidth()/2 ; - endPointY = y-selector.getPrefHeight()/2; - } - selectorTransition = new CachedTransition(selector, new Timeline(new KeyFrame(Duration.millis(1000), - new KeyValue(selector.translateXProperty(), endPointX, Interpolator.EASE_BOTH), - new KeyValue(selector.translateYProperty(), endPointY, Interpolator.EASE_BOTH)))){{ - setCycleDuration(Duration.millis(160)); - setDelay(Duration.seconds(0)); - }}; - - if(pTrans!=null) pTrans.stop(); - pTrans = new ParallelTransition(colorsTransition, selectorTransition); - pTrans.setOnFinished((finish)->{ - if(pTrans.getStatus().equals(Status.STOPPED)) - allowColorChange = true; - }); - pTrans.play(); - - refreshHSLCircle(); - } - - private void setColorAtLocation(int x, int y) { - if(allowColorChange){ - Color color = getColorAtLocation(x, y); - String colorString = "rgb("+color.getRed()*255+"," +color.getGreen()*255+ "," +color.getBlue()*255+ ");"; - colorNodes.forEach(node-> node.setStyle("-fx-background-color:" + colorString + "; -fx-fill:" + colorString+";")); - } - } - - private Color getColorAtLocation(double x, double y){ - double dy = x - centerX; - double dx = y - centerY; - return getColor(dx, dy); - } - - private Image getHuesCircle(int width, int height, List stops) { - WritableImage raster = new WritableImage(width, height); - PixelWriter pixelWriter = raster.getPixelWriter(); - Point2D center = new Point2D((double)width / 2, (double)height / 2); - double rsmall = 0.8*width / 2; - double rbig = (double)width / 2; - for (int y = 0 ; y < height ; y++) { - for (int x = 0 ; x < width ; x++) { - double dx = x - center.getX(); - double dy = y - center.getY(); - double distance = Math.sqrt((dx * dx) + (dy * dy)); - double o = Math.atan2(dy, dx); - if(distance > rsmall && distance < rbig){ - double H = map(o,-Math.PI, Math.PI, 0 , 255); - double S = 255; - double L = 152; - pixelWriter.setColor(x, y, HSL2RGB(H,S,L)); - } - } - } - return raster; - } - - private Image getSLCricle(int width, int height, List stops) { - WritableImage raster = new WritableImage(width, height); - PixelWriter pixelWriter = raster.getPixelWriter(); - Point2D center = new Point2D((double)width / 2, (double)height / 2); - for (int y = 0 ; y < height ; y++) { - for (int x = 0 ; x < width ; x++) { - double dy = x - center.getX(); - double dx = y - center.getY(); - pixelWriter.setColor(x, y, getColor(dx, dy)); - } - } - return raster; - } - - private double clamp(double from,double small,double big) { - return Math.min(Math.max(from,small),big); - } - - private Color getColor(double dx, double dy){ - double distance = Math.sqrt((dx * dx) + (dy * dy)); - double rverysmall = 0.65*((double)pickerSize/2); - Color pixelColor = Color.BLUE; - - if (distance <= rverysmall*1.1) { - double angle = -Math.PI/2.; - double angle1 = angle+2*Math.PI/3.; - double angle2 = angle1+2*Math.PI/3.; - double x1 = rverysmall*Math.sin(angle1); - double y1 = rverysmall*Math.cos(angle1); - double x2 = rverysmall*Math.sin(angle2); - double y2 = rverysmall*Math.cos(angle2); - dx += 0.01; - double[] circle = circleFrom3Points(new Point2D(x1,y1),new Point2D(x2,y2),new Point2D(dx,dy)); - double xArc = circle[0]; - double yArc = 0; - double arcR = circle[2]; - double Arco = Math.atan2(dx-xArc,dy-yArc); - double Arco1 = Math.atan2(x1-xArc,y1-yArc); - double Arco2 = Math.atan2(x2-xArc,y2-yArc); - - double finalX = xArc>0 ? xArc-arcR : xArc+arcR; - - double saturation = map(finalX,-rverysmall,rverysmall,255,0); - - double lightness = 255; - double diffAngle = Arco2 - Arco1; - double diffArco = Arco - Arco1; - if(dx rverysmall) { - saturation = 255-saturation; - if(lightness < 0 && dy < 0) { - lightness = 255; - } - } - lightness = clamp(lightness,0,255); - if((saturation < 10 && dx < x1) || (saturation >240 && dx > x1)) { - saturation = 255-saturation; - } - saturation = clamp(saturation,0,255); - pixelColor = HSL2RGB(currentHue,saturation,lightness); - } - return pixelColor; - } - - - /*************************************************************************** - * * - * Hues Animation * - * * - **************************************************************************/ - - private final class CurveTransition extends Transition { - Point2D from; - double fromTheta; - double toTheta; - public CurveTransition(Point2D from, Point2D to) { - this.from = from; - double fromDx = from.getX() - centerX; - double fromDy = from.getY() - centerY; - fromTheta = Math.atan2(fromDy, fromDx); - double toDx = to.getX() - centerX; - double toDy = to.getY() - centerY; - toTheta = Math.atan2(toDy, toDx); - setInterpolator(Interpolator.EASE_BOTH); - setDelay(Duration.millis(0)); - setCycleDuration(Duration.millis(240)); - } - @Override - protected void interpolate(double frac) { - double dif = Math.min(Math.abs(toTheta - fromTheta), 2*Math.PI - Math.abs(toTheta - fromTheta)); - if(dif == 2*Math.PI - Math.abs(toTheta - fromTheta)){ - int dir = -1; - if(toTheta < fromTheta) dir = 1; - dif = dir*dif; - } else dif = toTheta - fromTheta; - - Point2D newP = rotate(from, new Point2D(centerX,centerY), frac * dif ); - colorSelector.setRotate(90+Math.toDegrees(Math.atan2(newP.getY()-centerY, newP.getX()-centerX))); - colorSelector.setTranslateX(newP.getX() - colorSelector.getPrefWidth()/2); - colorSelector.setTranslateY(newP.getY() - colorSelector.getPrefHeight()/2); - } - } - - - - /*************************************************************************** - * * - * Util methods * - * * - **************************************************************************/ - - private double map(double val, double min1, double max1, double min2, double max2){ - return min2+(max2-min2)*((val-min1)/(max1-min1)); - } - - private Color HSL2RGB(double hue, double sat, double lum) - { - hue = map(hue,0,255,0,359); - sat = map(sat,0,255,0,1); - lum = map(lum,0,255,0,1); - double v; - double red, green, blue; - double m; - double sv; - int sextant; - double fract, vsf, mid1, mid2; - - red = lum; // default to gray - green = lum; - blue = lum; - v = (lum <= 0.5) ? (lum * (1.0 + sat)) : (lum + sat - lum * sat); - m = lum + lum - v; - sv = (v - m) / v; - hue /= 60.0; //get into range 0..6 - sextant = (int) Math.floor(hue); // int32 rounds up or down. - fract = hue - sextant; - vsf = v * sv * fract; - mid1 = m + vsf; - mid2 = v - vsf; - - if (v > 0) - { - switch (sextant) - { - case 0: red = v; green = mid1; blue = m; break; - case 1: red = mid2; green = v; blue = m; break; - case 2: red = m; green = v; blue = mid1; break; - case 3: red = m; green = mid2; blue = v; break; - case 4: red = mid1; green = m; blue = v; break; - case 5: red = v; green = m; blue = mid2; break; - } - } - return new Color(red, green, blue, 1); - } - - private double[] circleFrom3Points(Point2D a,Point2D b, Point2D c) { - double ax,ay,bx,by,cx,cy,x1,y11,dx1,dy1,x2,y2,dx2,dy2,ox,oy,dx,dy,radius; // Variables Used and to Declared - ax =a.getX() ; ay = a.getY(); //first Point X and Y - bx =b.getX(); by = b.getY(); // Second Point X and Y - cx =c.getX() ; cy =c.getY(); // Third Point X and Y - - ////****************Following are Basic Procedure**********************/// - x1 = (bx + ax) / 2; - y11 = (by + ay) / 2; - dy1 = bx - ax; - dx1 = -(by - ay); - - x2 = (cx + bx) / 2; - y2 = (cy + by) / 2; - dy2 = cx - bx; - dx2 = -(cy - by); - - ox = (y11 * dx1 * dx2 + x2 * dx1 * dy2 - x1 * dy1 * dx2 - y2 * dx1 * dx2)/ (dx1 * dy2 - dy1 * dx2); - oy = (ox - x1) * dy1 / dx1 + y11; - - dx = ox - ax; - dy = oy - ay; - radius =Math.sqrt(dx * dx + dy * dy); - double[] circle = {ox,oy,radius}; - return circle; - } - - - private Point2D getPointFromSL(int saturation, int lightness, double radius) { - double dy = map(saturation,0,255,-radius,radius); - double angle = 0.; - double angle1 = angle+2*Math.PI/3.; - double angle2 = angle1+2*Math.PI/3.; - double x1 = radius*Math.sin(angle1); - double y1 = radius*Math.cos(angle1); - double x2 = radius*Math.sin(angle2); - double y2 = radius*Math.cos(angle2); - double dx = 0; - double[] circle = circleFrom3Points(new Point2D(x1,y1),new Point2D(dx,dy),new Point2D(x2,y2)); - double xArc = circle[0]; - double yArc = circle[1]; - double arcR = circle[2]; - double Arco1 = Math.atan2(x1-xArc,y1-yArc); - double Arco2 = Math.atan2(x2-xArc,y2-yArc); - double ArcoFinal = map(lightness,0,255,Arco2,Arco1); - double finalX = xArc + arcR*Math.sin(ArcoFinal); - double finalY = yArc + arcR*Math.cos(ArcoFinal); - if(dy())); + // clip to smooth the edges + Circle outterCircle = new Circle(centerX, centerY, huesLargeR - 2); + Circle innterCircle = new Circle(centerX, centerY, huesSmallR + 2); + huesCircleView.setClip(Path.subtract(outterCircle, innterCircle)); + this.getChildren().add(huesCircleView); + + // create Hues Circle Selector + Circle r1 = new Circle(pickerRadius - huesSmallR); + Circle r2 = new Circle(pickerRadius - huesRadius); + colorSelector = new Pane(); + colorSelector.setStyle("-fx-border-color:#424242; -fx-border-width:1px; -fx-background-color:rgba(255, 255, 255, 0.87);"); + colorSelector.setPrefSize(pickerRadius - huesSmallR,pickerRadius - huesSmallR); + colorSelector.setShape(Path.subtract(r1, r2)); + colorSelector.setCache(true); +// JFXDepthManager.setDepth(colorSelector, 1); + colorSelector.setMouseTransparent(true); + colorSelector.setPickOnBounds(false); + this.getChildren().add(colorSelector); + + // add Hues Selection Listeners + huesCircleView.addEventHandler(MouseEvent.MOUSE_DRAGGED, (event)->{ + if(colorsTransition!=null) colorsTransition.stop(); + double dx = event.getX() - centerX; + double dy = event.getY() - centerY; + double theta = Math.atan2(dy, dx); + double x = centerX + huesRadius * Math.cos(theta); + double y = centerY + huesRadius * Math.sin(theta); + colorSelector.setRotate(90+Math.toDegrees(Math.atan2(dy, dx))); + colorSelector.setTranslateX(x - colorSelector.getPrefWidth()/2); + colorSelector.setTranslateY(y - colorSelector.getPrefHeight()/2); + }); + huesCircleView.addEventHandler(MouseEvent.MOUSE_PRESSED, (event)->{ + double dx = event.getX() - centerX; + double dy = event.getY() - centerY; + double theta = Math.atan2(dy, dx); + double x = centerX + huesRadius * Math.cos(theta); + double y = centerY + huesRadius * Math.sin(theta); + colorsTransition = new CurveTransition(new Point2D(colorSelector.getTranslateX() + colorSelector.getPrefWidth()/2, colorSelector.getTranslateY() + colorSelector.getPrefHeight()/2), new Point2D(x,y)); + colorsTransition.play(); + }); + colorSelector.translateXProperty().addListener((o,oldVal,newVal)-> updateHSLCircleColor((int) (newVal.intValue() + colorSelector.getPrefWidth()/2), (int) (colorSelector.getTranslateY() + colorSelector.getPrefHeight()/2))); + colorSelector.translateYProperty().addListener((o,oldVal,newVal)-> updateHSLCircleColor((int) (colorSelector.getTranslateX() + colorSelector.getPrefWidth()/2), (int) (newVal.intValue() + colorSelector.getPrefHeight()/2))); + + + // Create SL Circle + slCircleView = new ImageView(getSLCricle(pickerSize,pickerSize,new ArrayList())); + slCircleView.setClip(new Circle(centerX,centerY, slRadius - 2)); + slCircleView.setPickOnBounds(false); + this.getChildren().add(slCircleView); + + // create SL Circle Selector + selector = new Pane(); + Circle c1 = new Circle(selectorSize/2); + Circle c2 = new Circle((selectorSize/2) * 0.5); + selector.setShape(Path.subtract(c1, c2)); + selector.setStyle("-fx-border-color:#424242; -fx-border-width:1px;-fx-background-color:rgba(255, 255, 255, 0.87);"); + selector.setPrefSize(selectorSize, selectorSize); + selector.setMinSize(selectorSize, selectorSize); + selector.setMaxSize(selectorSize, selectorSize); +// JFXDepthManager.setDepth(selector, 1); + selector.setCache(true); + selector.setMouseTransparent(true); + this.getChildren().add(selector); + + + // add SL selection Listeners + slCircleView.addEventHandler(MouseEvent.MOUSE_DRAGGED, (event)->{ + if(selectorTransition!=null) selectorTransition.stop(); + if(Math.pow(event.getX()-centerX, 2) + Math.pow( event.getY()-centerY, 2) < Math.pow(slRadius-2, 2)){ + selector.setTranslateX(event.getX()-selector.getPrefWidth()/2); + selector.setTranslateY(event.getY()-selector.getPrefHeight()/2); + }else{ + double dx = event.getX() - centerX; + double dy = event.getY() - centerY; + double theta = Math.atan2(dy, dx); + double x = centerX + (slRadius-2) * Math.cos(theta); + double y = centerY + (slRadius-2) * Math.sin(theta); + selector.setTranslateX(x-selector.getPrefWidth()/2); + selector.setTranslateY(y-selector.getPrefHeight()/2); + } + }); + slCircleView.addEventHandler(MouseEvent.MOUSE_PRESSED, (event)->{ + selectorTransition = new CachedTransition(selector, new Timeline(new KeyFrame(Duration.millis(1000), + new KeyValue(selector.translateXProperty(), event.getX()-selector.getPrefWidth()/2, Interpolator.EASE_BOTH), + new KeyValue(selector.translateYProperty(), event.getY()-selector.getPrefHeight()/2, Interpolator.EASE_BOTH)))){{ + setCycleDuration(Duration.millis(160)); + setDelay(Duration.seconds(0)); + }}; + selectorTransition.play(); + }); + // add slCircleView listener + selector.translateXProperty().addListener((o,oldVal,newVal)-> setColorAtLocation((int)newVal.intValue()+selectorSize/2, (int)selector.getTranslateY()+selectorSize/2)); + selector.translateYProperty().addListener((o,oldVal,newVal)-> setColorAtLocation((int)selector.getTranslateX()+selectorSize/2, (int)newVal.intValue()+selectorSize/2)); + + + + // initial color selection + double dx = 20 - centerX; + double dy = 20 - centerY; + double theta = Math.atan2(dy, dx); + double x = centerX + huesRadius * Math.cos(theta); + double y = centerY + huesRadius * Math.sin(theta); + colorSelector.setRotate(90+Math.toDegrees(Math.atan2(dy, dx))); + colorSelector.setTranslateX(x-colorSelector.getPrefWidth()/2); + colorSelector.setTranslateY(y-colorSelector.getPrefHeight()/2); + selector.setTranslateX(centerX-selector.getPrefWidth()/2); + selector.setTranslateY(centerY-selector.getPrefHeight()/2); + + } + + + /** + * List of Color Nodes that needs to be updated when picking a color + */ + private ObservableList colorNodes = FXCollections.observableArrayList(); + + public void addColorSelectionNode(Node... nodes){ + colorNodes.addAll(nodes); + } + public void removeColorSelectionNode(Node... nodes){ + colorNodes.removeAll(nodes); + } + + private void updateHSLCircleColor(int x, int y) { + // transform color to HSL space + Color color = huesCircleView.getImage().getPixelReader().getColor(x, y); + double max = Math.max(color.getRed(), Math.max(color.getGreen(), color.getBlue())), min = Math.min(color.getRed(), Math.min(color.getGreen(), color.getBlue())); + double hue = 0; + if(max != min){ + double d = max - min; + if(max == color.getRed()){ + hue = (color.getGreen() - color.getBlue()) / d + (color.getGreen() < color.getBlue() ? 6 : 0); + }else if(max == color.getGreen()){ + hue = (color.getBlue() - color.getRed()) / d + 2; + }else if(max == color.getBlue()){ + hue = (color.getRed() - color.getGreen()) / d + 4; + } + hue /= 6; + } + currentHue = map(hue, 0,1, 0,255); + + // refresh the HSL circle + refreshHSLCircle(); + } + + private void refreshHSLCircle() { + ColorAdjust colorAdjust = new ColorAdjust(); + colorAdjust.setHue(map(currentHue + (currentHue<127.5? 1:-1)*127.5, 0,255, -1 , 1)); + slCircleView.setEffect(colorAdjust); + setColorAtLocation((int)selector.getTranslateX()+selectorSize/2, (int)selector.getTranslateY()+selectorSize/2); + } + + + /** + * this method is used to move selectors to a certain color + */ + private boolean allowColorChange = true; + private ParallelTransition pTrans; + + public void moveToColor(Color color){ + allowColorChange = false; + double max = Math.max(color.getRed(), Math.max(color.getGreen(), color.getBlue())), min = Math.min(color.getRed(), Math.min(color.getGreen(), color.getBlue())); + double hue = 0; + double l = (max + min) / 2; + double s = 0; + if(max == min){ + hue = s = 0; // achromatic + } else{ + double d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + if(max == color.getRed()){ + hue = (color.getGreen() - color.getBlue()) / d + (color.getGreen() < color.getBlue() ? 6 : 0); + }else if(max == color.getGreen()){ + hue = (color.getBlue() - color.getRed()) / d + 2; + }else if(max == color.getBlue()){ + hue = (color.getRed() - color.getGreen()) / d + 4; + } + hue /= 6; + } + currentHue = map(hue, 0,1, 0,255); + + // Animate Hue + double theta = map(currentHue, 0,255, -Math.PI, Math.PI); + double x = centerX + huesRadius * Math.cos(theta); + double y = centerY + huesRadius * Math.sin(theta); + colorsTransition = new CurveTransition(new Point2D(colorSelector.getTranslateX() + colorSelector.getPrefWidth()/2, colorSelector.getTranslateY() + colorSelector.getPrefHeight()/2), new Point2D(x , y )); + + + // Animate SL + s = map(s, 0,1, 0,255); + l = map(l, 0,1, 0,255); + Point2D point = getPointFromSL((int)s, (int)l, slRadius); + double pX = centerX - point.getX(); + double pY = centerY - point.getY(); + + double endPointX; + double endPointY; + if(Math.pow(pX-centerX, 2) + Math.pow(pY-centerY, 2) < Math.pow(slRadius-2,2)){ + endPointX = pX-selector.getPrefWidth()/2 ; + endPointY = pY-selector.getPrefHeight()/2; + }else{ + double dx = pX - centerX; + double dy = pY - centerY; + theta = Math.atan2(dy, dx); + x = centerX + (slRadius-2) * Math.cos(theta); + y = centerY + (slRadius-2) * Math.sin(theta); + endPointX = x-selector.getPrefWidth()/2 ; + endPointY = y-selector.getPrefHeight()/2; + } + selectorTransition = new CachedTransition(selector, new Timeline(new KeyFrame(Duration.millis(1000), + new KeyValue(selector.translateXProperty(), endPointX, Interpolator.EASE_BOTH), + new KeyValue(selector.translateYProperty(), endPointY, Interpolator.EASE_BOTH)))){{ + setCycleDuration(Duration.millis(160)); + setDelay(Duration.seconds(0)); + }}; + + if(pTrans!=null) pTrans.stop(); + pTrans = new ParallelTransition(colorsTransition, selectorTransition); + pTrans.setOnFinished((finish)->{ + if(pTrans.getStatus().equals(Status.STOPPED)) + allowColorChange = true; + }); + pTrans.play(); + + refreshHSLCircle(); + } + + private void setColorAtLocation(int x, int y) { + if(allowColorChange){ + Color color = getColorAtLocation(x, y); + String colorString = "rgb("+color.getRed()*255+"," +color.getGreen()*255+ "," +color.getBlue()*255+ ");"; + colorNodes.forEach(node-> node.setStyle("-fx-background-color:" + colorString + "; -fx-fill:" + colorString+";")); + } + } + + private Color getColorAtLocation(double x, double y){ + double dy = x - centerX; + double dx = y - centerY; + return getColor(dx, dy); + } + + private Image getHuesCircle(int width, int height, List stops) { + WritableImage raster = new WritableImage(width, height); + PixelWriter pixelWriter = raster.getPixelWriter(); + Point2D center = new Point2D((double)width / 2, (double)height / 2); + double rsmall = 0.8*width / 2; + double rbig = (double)width / 2; + for (int y = 0 ; y < height ; y++) { + for (int x = 0 ; x < width ; x++) { + double dx = x - center.getX(); + double dy = y - center.getY(); + double distance = Math.sqrt((dx * dx) + (dy * dy)); + double o = Math.atan2(dy, dx); + if(distance > rsmall && distance < rbig){ + double H = map(o,-Math.PI, Math.PI, 0 , 255); + double S = 255; + double L = 152; + pixelWriter.setColor(x, y, HSL2RGB(H,S,L)); + } + } + } + return raster; + } + + private Image getSLCricle(int width, int height, List stops) { + WritableImage raster = new WritableImage(width, height); + PixelWriter pixelWriter = raster.getPixelWriter(); + Point2D center = new Point2D((double)width / 2, (double)height / 2); + for (int y = 0 ; y < height ; y++) { + for (int x = 0 ; x < width ; x++) { + double dy = x - center.getX(); + double dx = y - center.getY(); + pixelWriter.setColor(x, y, getColor(dx, dy)); + } + } + return raster; + } + + private double clamp(double from,double small,double big) { + return Math.min(Math.max(from,small),big); + } + + private Color getColor(double dx, double dy){ + double distance = Math.sqrt((dx * dx) + (dy * dy)); + double rverysmall = 0.65*((double)pickerSize/2); + Color pixelColor = Color.BLUE; + + if (distance <= rverysmall*1.1) { + double angle = -Math.PI/2.; + double angle1 = angle+2*Math.PI/3.; + double angle2 = angle1+2*Math.PI/3.; + double x1 = rverysmall*Math.sin(angle1); + double y1 = rverysmall*Math.cos(angle1); + double x2 = rverysmall*Math.sin(angle2); + double y2 = rverysmall*Math.cos(angle2); + dx += 0.01; + double[] circle = circleFrom3Points(new Point2D(x1,y1),new Point2D(x2,y2),new Point2D(dx,dy)); + double xArc = circle[0]; + double yArc = 0; + double arcR = circle[2]; + double Arco = Math.atan2(dx-xArc,dy-yArc); + double Arco1 = Math.atan2(x1-xArc,y1-yArc); + double Arco2 = Math.atan2(x2-xArc,y2-yArc); + + double finalX = xArc>0 ? xArc-arcR : xArc+arcR; + + double saturation = map(finalX,-rverysmall,rverysmall,255,0); + + double lightness = 255; + double diffAngle = Arco2 - Arco1; + double diffArco = Arco - Arco1; + if(dx rverysmall) { + saturation = 255-saturation; + if(lightness < 0 && dy < 0) { + lightness = 255; + } + } + lightness = clamp(lightness,0,255); + if((saturation < 10 && dx < x1) || (saturation >240 && dx > x1)) { + saturation = 255-saturation; + } + saturation = clamp(saturation,0,255); + pixelColor = HSL2RGB(currentHue,saturation,lightness); + } + return pixelColor; + } + + + /*************************************************************************** + * * + * Hues Animation * + * * + **************************************************************************/ + + private final class CurveTransition extends Transition { + Point2D from; + double fromTheta; + double toTheta; + public CurveTransition(Point2D from, Point2D to) { + this.from = from; + double fromDx = from.getX() - centerX; + double fromDy = from.getY() - centerY; + fromTheta = Math.atan2(fromDy, fromDx); + double toDx = to.getX() - centerX; + double toDy = to.getY() - centerY; + toTheta = Math.atan2(toDy, toDx); + setInterpolator(Interpolator.EASE_BOTH); + setDelay(Duration.millis(0)); + setCycleDuration(Duration.millis(240)); + } + @Override + protected void interpolate(double frac) { + double dif = Math.min(Math.abs(toTheta - fromTheta), 2*Math.PI - Math.abs(toTheta - fromTheta)); + if(dif == 2*Math.PI - Math.abs(toTheta - fromTheta)){ + int dir = -1; + if(toTheta < fromTheta) dir = 1; + dif = dir*dif; + } else dif = toTheta - fromTheta; + + Point2D newP = rotate(from, new Point2D(centerX,centerY), frac * dif ); + colorSelector.setRotate(90+Math.toDegrees(Math.atan2(newP.getY()-centerY, newP.getX()-centerX))); + colorSelector.setTranslateX(newP.getX() - colorSelector.getPrefWidth()/2); + colorSelector.setTranslateY(newP.getY() - colorSelector.getPrefHeight()/2); + } + } + + + + /*************************************************************************** + * * + * Util methods * + * * + **************************************************************************/ + + private double map(double val, double min1, double max1, double min2, double max2){ + return min2+(max2-min2)*((val-min1)/(max1-min1)); + } + + private Color HSL2RGB(double hue, double sat, double lum) + { + hue = map(hue,0,255,0,359); + sat = map(sat,0,255,0,1); + lum = map(lum,0,255,0,1); + double v; + double red, green, blue; + double m; + double sv; + int sextant; + double fract, vsf, mid1, mid2; + + red = lum; // default to gray + green = lum; + blue = lum; + v = (lum <= 0.5) ? (lum * (1.0 + sat)) : (lum + sat - lum * sat); + m = lum + lum - v; + sv = (v - m) / v; + hue /= 60.0; //get into range 0..6 + sextant = (int) Math.floor(hue); // int32 rounds up or down. + fract = hue - sextant; + vsf = v * sv * fract; + mid1 = m + vsf; + mid2 = v - vsf; + + if (v > 0) + { + switch (sextant) + { + case 0: red = v; green = mid1; blue = m; break; + case 1: red = mid2; green = v; blue = m; break; + case 2: red = m; green = v; blue = mid1; break; + case 3: red = m; green = mid2; blue = v; break; + case 4: red = mid1; green = m; blue = v; break; + case 5: red = v; green = m; blue = mid2; break; + } + } + return new Color(red, green, blue, 1); + } + + private double[] circleFrom3Points(Point2D a,Point2D b, Point2D c) { + double ax,ay,bx,by,cx,cy,x1,y11,dx1,dy1,x2,y2,dx2,dy2,ox,oy,dx,dy,radius; // Variables Used and to Declared + ax =a.getX() ; ay = a.getY(); //first Point X and Y + bx =b.getX(); by = b.getY(); // Second Point X and Y + cx =c.getX() ; cy =c.getY(); // Third Point X and Y + + ////****************Following are Basic Procedure**********************/// + x1 = (bx + ax) / 2; + y11 = (by + ay) / 2; + dy1 = bx - ax; + dx1 = -(by - ay); + + x2 = (cx + bx) / 2; + y2 = (cy + by) / 2; + dy2 = cx - bx; + dx2 = -(cy - by); + + ox = (y11 * dx1 * dx2 + x2 * dx1 * dy2 - x1 * dy1 * dx2 - y2 * dx1 * dx2)/ (dx1 * dy2 - dy1 * dx2); + oy = (ox - x1) * dy1 / dx1 + y11; + + dx = ox - ax; + dy = oy - ay; + radius =Math.sqrt(dx * dx + dy * dy); + double[] circle = {ox,oy,radius}; + return circle; + } + + + private Point2D getPointFromSL(int saturation, int lightness, double radius) { + double dy = map(saturation,0,255,-radius,radius); + double angle = 0.; + double angle1 = angle+2*Math.PI/3.; + double angle2 = angle1+2*Math.PI/3.; + double x1 = radius*Math.sin(angle1); + double y1 = radius*Math.cos(angle1); + double x2 = radius*Math.sin(angle2); + double y2 = radius*Math.cos(angle2); + double dx = 0; + double[] circle = circleFrom3Points(new Point2D(x1,y1),new Point2D(dx,dy),new Point2D(x2,y2)); + double xArc = circle[0]; + double yArc = circle[1]; + double arcR = circle[2]; + double Arco1 = Math.atan2(x1-xArc,y1-yArc); + double Arco2 = Math.atan2(x2-xArc,y2-yArc); + double ArcoFinal = map(lightness,0,255,Arco2,Arco1); + double finalX = xArc + arcR*Math.sin(ArcoFinal); + double finalY = yArc + arcR*Math.cos(ArcoFinal); + if(dyMaterial Design ComboBox Skin - * - * @author Shadi Shaheen - * @version 2.0 - * @since 2017-01-25 - */ - -public class JFXComboBoxListViewSkin extends ComboBoxListViewSkin { - - /*************************************************************************** - * * - * Private fields * - * * - **************************************************************************/ - - private boolean invalid = true; - - private StackPane customPane; - private StackPane line = new StackPane(); - private StackPane focusedLine = new StackPane(); - private Text promptText = new Text(); - - private double initScale = 0.05; - 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 ParallelTransition transition; - private CachedTransition promptTextUpTransition; - private CachedTransition promptTextDownTransition; - private CachedTransition promptTextColorTransition; - private Scale promptTextScale = new Scale(1,1,0,0); - private Paint oldPromptTextFill; - protected final ObjectProperty promptTextFill = new SimpleObjectProperty(Color.valueOf("#B2B2B2")); - - private BooleanBinding usePromptText = Bindings.createBooleanBinding(()-> usePromptText(), ((JFXComboBox)getSkinnable()).valueProperty(), getSkinnable().promptTextProperty()); - - - /*************************************************************************** - * * - * Constructors * - * * - **************************************************************************/ - - public JFXComboBoxListViewSkin(final JFXComboBox comboBox) { - - super(comboBox); - // customize combox box - arrowButton.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, null, null))); - - // create my custom pane for the prompt node - promptText.textProperty().bind(comboBox.promptTextProperty()); - promptText.fillProperty().bind(promptTextFill); - promptText.getStyleClass().addAll("text","prompt-text"); - promptText.getTransforms().add(promptTextScale); - if(!comboBox.isLabelFloat()) promptText.visibleProperty().bind(usePromptText); - - customPane = new StackPane(); - customPane.setMouseTransparent(true); - customPane.getStyleClass().add("combo-box-button-container"); - customPane.backgroundProperty().bindBidirectional(getSkinnable().backgroundProperty()); - customPane.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, null, null))); - customPane.getChildren().add(promptText); - getChildren().add(0,customPane); - StackPane.setAlignment(promptText, Pos.CENTER_LEFT); - - // add lines - line.getStyleClass().add("input-line"); - focusedLine.getStyleClass().add("input-focused-line"); - getChildren().add(line); - getChildren().add(focusedLine); - line.setPrefHeight(1); - line.setTranslateY(1); // translate = prefHeight + init_translation - line.setBackground(new Background(new BackgroundFill(((JFXComboBox)getSkinnable()).getUnFocusColor(), - CornerRadii.EMPTY, Insets.EMPTY))); - if(getSkinnable().isDisabled()) { - line.setBorder(new Border(new BorderStroke(((JFXComboBox) 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(((JFXComboBox)getSkinnable()).getFocusColor(), - CornerRadii.EMPTY, Insets.EMPTY))); - focusedLine.setOpacity(0); - focusedLine.getTransforms().add(scale); - - if(comboBox.isEditable()){ - comboBox.getEditor().setStyle("-fx-background-color:TRANSPARENT;-fx-padding: 4 0 4 0"); - comboBox.getEditor().promptTextProperty().unbind(); - comboBox.getEditor().setPromptText(null); - comboBox.getEditor().textProperty().addListener((o,oldVal,newVal)-> usePromptText.invalidate()); - - comboBox.getEditor().textProperty().addListener((o,oldVal,newVal)->{ - comboBox.setValue(getConverter().fromString(newVal)); - }); - } - - comboBox.labelFloatProperty().addListener((o,oldVal,newVal)->{ - if(newVal) { - promptText.visibleProperty().unbind(); - JFXUtilities.runInFX(()->createFloatingAnimation()); - } - else promptText.visibleProperty().bind(usePromptText); - createFocusTransition(); - }); - - comboBox.focusColorProperty().addListener((o,oldVal,newVal)->{ - if(newVal!=null) { - focusedLine.setBackground(new Background(new BackgroundFill(newVal, CornerRadii.EMPTY, Insets.EMPTY))); - if(((JFXComboBox)getSkinnable()).isLabelFloat()){ - promptTextColorTransition = new CachedTransition(customPane, 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; - } - } - }); - - comboBox.unFocusColorProperty().addListener((o,oldVal,newVal)->{ - if(newVal!=null) - line.setBackground(new Background(new BackgroundFill(newVal, CornerRadii.EMPTY, Insets.EMPTY))); - }); - - comboBox.disabledProperty().addListener((o,oldVal,newVal) -> { - line.setBorder(newVal ? new Border(new BorderStroke(((JFXComboBox)getSkinnable()).getUnFocusColor(), - BorderStrokeStyle.DASHED, CornerRadii.EMPTY, new BorderWidths(line.getHeight()))) : Border.EMPTY); - line.setBackground(new Background(new BackgroundFill( newVal? Color.TRANSPARENT : ((JFXComboBox)getSkinnable()).getUnFocusColor(), - CornerRadii.EMPTY, Insets.EMPTY))); - }); - - // handle animation on focus gained/lost event - comboBox.focusedProperty().addListener((o,oldVal,newVal) -> { - if (newVal) focus(); - else unFocus(); - }); - - // handle animation on value changed - comboBox.valueProperty().addListener((o,oldVal,newVal)->{ - if(((JFXComboBox)getSkinnable()).isLabelFloat()){ - if(newVal == null || newVal.toString().isEmpty()) animateFloatingLabel(false); - else animateFloatingLabel(true); - } - }); - - } - - /*************************************************************************** - * * - * Public API * - * * - **************************************************************************/ - - @Override protected void layoutChildren(final double x, final double y, - final double w, final double h) { - super.layoutChildren(x,y,w,h); - customPane.resizeRelocate(x, y, w , h); - if(invalid){ - invalid = false; - // create floating label - createFloatingAnimation(); - } - focusedLine.resizeRelocate(x, getSkinnable().getHeight(), w, focusedLine.prefHeight(-1)); - line.resizeRelocate(x, getSkinnable().getHeight(), w, line.prefHeight(-1)); - scale.setPivotX(w/2); - } - - private void createFloatingAnimation() { - // TODO: the 6.05 should be computed, for now its hard coded to keep the alignment with other controls - promptTextUpTransition = new CachedTransition(customPane, new Timeline( - new KeyFrame(Duration.millis(1300), - new KeyValue(promptText.translateYProperty(), -customPane.getHeight() + 6.05 , 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(customPane, new Timeline( - new KeyFrame(Duration.millis(1300), - new KeyValue(promptTextFill, ((JFXComboBox)getSkinnable()).getFocusColor(), Interpolator.EASE_BOTH)))) - { - { setDelay(Duration.millis(0)); setCycleDuration(Duration.millis(160)); } - protected void starting() {super.starting(); oldPromptTextFill = promptTextFill.get();}; - }; - - promptTextDownTransition = new CachedTransition(customPane, 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); - }); - } - - private void focus(){ - // create the focus animations - if(transition == null) createFocusTransition(); - transition.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.getChildren().remove(promptTextColorTransition); - transition = null; - } - if(up && promptText.getTranslateY() == 0){ - promptTextDownTransition.stop(); - promptTextUpTransition.play(); - if(getSkinnable().isFocused()) promptTextColorTransition.play(); - }else if(!up){ - promptTextUpTransition.stop(); - if(getSkinnable().isFocused()) promptTextFill.set(oldPromptTextFill); - promptTextDownTransition.play(); - } - } - } - - private void createFocusTransition() { - transition = new ParallelTransition(); - if(((JFXComboBox)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(((JFXComboBox)getSkinnable()).isLabelFloat() && oldPromptTextFill != null){ - promptTextFill.set(oldPromptTextFill); - if(usePromptText()) promptTextDownTransition.play(); - } - } - - private boolean usePromptText() { - Object txt = ((JFXComboBox)getSkinnable()).getValue(); - String promptTxt = getSkinnable().getPromptText(); - boolean hasPromptText = (txt == null || txt.toString().isEmpty()) && promptTxt != null && !promptTxt.isEmpty() && !promptTextFill.get().equals(Color.TRANSPARENT); - return hasPromptText; - } -} +/* + * 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.skins; + +import com.jfoenix.concurrency.JFXUtilities; +import com.jfoenix.controls.JFXComboBox; +import com.jfoenix.transitions.CachedTransition; +import com.sun.javafx.scene.control.skin.ComboBoxListViewSkin; + +import javafx.animation.Interpolator; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.ParallelTransition; +import javafx.animation.Timeline; +import javafx.application.Platform; +import javafx.beans.binding.Bindings; +import javafx.beans.binding.BooleanBinding; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.layout.Background; +import javafx.scene.layout.BackgroundFill; +import javafx.scene.layout.Border; +import javafx.scene.layout.BorderStroke; +import javafx.scene.layout.BorderStrokeStyle; +import javafx.scene.layout.BorderWidths; +import javafx.scene.layout.CornerRadii; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; +import javafx.scene.text.Text; +import javafx.scene.transform.Scale; +import javafx.util.Duration; + +/** + *

Material Design ComboBox Skin

+ * + * @author Shadi Shaheen + * @version 2.0 + * @since 2017-01-25 + */ + +public class JFXComboBoxListViewSkin extends ComboBoxListViewSkin { + + /*************************************************************************** + * * + * Private fields * + * * + **************************************************************************/ + + private boolean invalid = true; + + private StackPane customPane; + private StackPane line = new StackPane(); + private StackPane focusedLine = new StackPane(); + private Text promptText = new Text(); + + private double initScale = 0.05; + 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 ParallelTransition transition; + private CachedTransition promptTextUpTransition; + private CachedTransition promptTextDownTransition; + private CachedTransition promptTextColorTransition; + private Scale promptTextScale = new Scale(1,1,0,0); + private Paint oldPromptTextFill; + protected final ObjectProperty promptTextFill = new SimpleObjectProperty(Color.valueOf("#B2B2B2")); + + private BooleanBinding usePromptText = Bindings.createBooleanBinding(()-> usePromptText(), ((JFXComboBox)getSkinnable()).valueProperty(), getSkinnable().promptTextProperty()); + + + /*************************************************************************** + * * + * Constructors * + * * + **************************************************************************/ + + public JFXComboBoxListViewSkin(final JFXComboBox comboBox) { + + super(comboBox); + // customize combox box + arrowButton.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, null, null))); + + // create my custom pane for the prompt node + promptText.textProperty().bind(comboBox.promptTextProperty()); + promptText.fillProperty().bind(promptTextFill); + promptText.getStyleClass().addAll("text","prompt-text"); + promptText.getTransforms().add(promptTextScale); + if(!comboBox.isLabelFloat()) promptText.visibleProperty().bind(usePromptText); + + customPane = new StackPane(); + customPane.setMouseTransparent(true); + customPane.getStyleClass().add("combo-box-button-container"); + customPane.backgroundProperty().bindBidirectional(getSkinnable().backgroundProperty()); + customPane.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, null, null))); + customPane.getChildren().add(promptText); + getChildren().add(0,customPane); + StackPane.setAlignment(promptText, Pos.CENTER_LEFT); + + // add lines + line.getStyleClass().add("input-line"); + focusedLine.getStyleClass().add("input-focused-line"); + getChildren().add(line); + getChildren().add(focusedLine); + line.setPrefHeight(1); + line.setTranslateY(1); // translate = prefHeight + init_translation + line.setBackground(new Background(new BackgroundFill(((JFXComboBox)getSkinnable()).getUnFocusColor(), + CornerRadii.EMPTY, Insets.EMPTY))); + if(getSkinnable().isDisabled()) { + line.setBorder(new Border(new BorderStroke(((JFXComboBox) 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(((JFXComboBox)getSkinnable()).getFocusColor(), + CornerRadii.EMPTY, Insets.EMPTY))); + focusedLine.setOpacity(0); + focusedLine.getTransforms().add(scale); + + if(comboBox.isEditable()){ + comboBox.getEditor().setStyle("-fx-background-color:TRANSPARENT;-fx-padding: 4 0 4 0"); + comboBox.getEditor().promptTextProperty().unbind(); + comboBox.getEditor().setPromptText(null); + comboBox.getEditor().textProperty().addListener((o,oldVal,newVal)-> usePromptText.invalidate()); + + comboBox.getEditor().textProperty().addListener((o,oldVal,newVal)->{ + comboBox.setValue(getConverter().fromString(newVal)); + }); + } + + comboBox.labelFloatProperty().addListener((o,oldVal,newVal)->{ + if(newVal) { + promptText.visibleProperty().unbind(); + JFXUtilities.runInFX(()->createFloatingAnimation()); + } + else promptText.visibleProperty().bind(usePromptText); + createFocusTransition(); + }); + + comboBox.focusColorProperty().addListener((o,oldVal,newVal)->{ + if(newVal!=null) { + focusedLine.setBackground(new Background(new BackgroundFill(newVal, CornerRadii.EMPTY, Insets.EMPTY))); + if(((JFXComboBox)getSkinnable()).isLabelFloat()){ + promptTextColorTransition = new CachedTransition(customPane, 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; + } + } + }); + + comboBox.unFocusColorProperty().addListener((o,oldVal,newVal)->{ + if(newVal!=null) + line.setBackground(new Background(new BackgroundFill(newVal, CornerRadii.EMPTY, Insets.EMPTY))); + }); + + comboBox.disabledProperty().addListener((o,oldVal,newVal) -> { + line.setBorder(newVal ? new Border(new BorderStroke(((JFXComboBox)getSkinnable()).getUnFocusColor(), + BorderStrokeStyle.DASHED, CornerRadii.EMPTY, new BorderWidths(line.getHeight()))) : Border.EMPTY); + line.setBackground(new Background(new BackgroundFill( newVal? Color.TRANSPARENT : ((JFXComboBox)getSkinnable()).getUnFocusColor(), + CornerRadii.EMPTY, Insets.EMPTY))); + }); + + // handle animation on focus gained/lost event + comboBox.focusedProperty().addListener((o,oldVal,newVal) -> { + if (newVal) focus(); + else unFocus(); + }); + + // handle animation on value changed + comboBox.valueProperty().addListener((o,oldVal,newVal)->{ + if(((JFXComboBox)getSkinnable()).isLabelFloat()){ + if(newVal == null || newVal.toString().isEmpty()) animateFloatingLabel(false); + else animateFloatingLabel(true); + } + }); + + } + + /*************************************************************************** + * * + * Public API * + * * + **************************************************************************/ + + @Override protected void layoutChildren(final double x, final double y, + final double w, final double h) { + super.layoutChildren(x,y,w,h); + customPane.resizeRelocate(x, y, w , h); + if(invalid){ + invalid = false; + // create floating label + createFloatingAnimation(); + } + focusedLine.resizeRelocate(x, getSkinnable().getHeight(), w, focusedLine.prefHeight(-1)); + line.resizeRelocate(x, getSkinnable().getHeight(), w, line.prefHeight(-1)); + scale.setPivotX(w/2); + } + + private void createFloatingAnimation() { + // TODO: the 6.05 should be computed, for now its hard coded to keep the alignment with other controls + promptTextUpTransition = new CachedTransition(customPane, new Timeline( + new KeyFrame(Duration.millis(1300), + new KeyValue(promptText.translateYProperty(), -customPane.getHeight() + 6.05 , 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(customPane, new Timeline( + new KeyFrame(Duration.millis(1300), + new KeyValue(promptTextFill, ((JFXComboBox)getSkinnable()).getFocusColor(), Interpolator.EASE_BOTH)))) + { + { setDelay(Duration.millis(0)); setCycleDuration(Duration.millis(160)); } + protected void starting() {super.starting(); oldPromptTextFill = promptTextFill.get();}; + }; + + promptTextDownTransition = new CachedTransition(customPane, 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); + }); + } + + private void focus(){ + // create the focus animations + if(transition == null) createFocusTransition(); + transition.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.getChildren().remove(promptTextColorTransition); + transition = null; + } + if(up && promptText.getTranslateY() == 0){ + promptTextDownTransition.stop(); + promptTextUpTransition.play(); + if(getSkinnable().isFocused()) promptTextColorTransition.play(); + }else if(!up){ + promptTextUpTransition.stop(); + if(getSkinnable().isFocused()) promptTextFill.set(oldPromptTextFill); + promptTextDownTransition.play(); + } + } + } + + private void createFocusTransition() { + transition = new ParallelTransition(); + if(((JFXComboBox)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(((JFXComboBox)getSkinnable()).isLabelFloat() && oldPromptTextFill != null){ + promptTextFill.set(oldPromptTextFill); + if(usePromptText()) promptTextDownTransition.play(); + } + } + + private boolean usePromptText() { + Object txt = ((JFXComboBox)getSkinnable()).getValue(); + String promptTxt = getSkinnable().getPromptText(); + boolean hasPromptText = (txt == null || txt.toString().isEmpty()) && promptTxt != null && !promptTxt.isEmpty() && !promptTextFill.get().equals(Color.TRANSPARENT); + return hasPromptText; + } +} diff --git a/src/com/jfoenix/skins/JFXCustomColorPicker.java b/jfoenix/src/main/java/com/jfoenix/skins/JFXCustomColorPicker.java similarity index 97% rename from src/com/jfoenix/skins/JFXCustomColorPicker.java rename to jfoenix/src/main/java/com/jfoenix/skins/JFXCustomColorPicker.java index 887722be..5976a47e 100644 --- a/src/com/jfoenix/skins/JFXCustomColorPicker.java +++ b/jfoenix/src/main/java/com/jfoenix/skins/JFXCustomColorPicker.java @@ -1,697 +1,697 @@ -/* - * 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.skins; - -import com.jfoenix.effects.JFXDepthManager; -import com.jfoenix.transitions.CachedTransition; -import javafx.animation.Interpolator; -import javafx.animation.KeyFrame; -import javafx.animation.KeyValue; -import javafx.animation.Timeline; -import javafx.beans.binding.Bindings; -import javafx.beans.property.DoubleProperty; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleDoubleProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.geometry.Point2D; -import javafx.scene.Node; -import javafx.scene.effect.DropShadow; -import javafx.scene.input.MouseEvent; -import javafx.scene.layout.Pane; -import javafx.scene.paint.Color; -import javafx.scene.shape.*; -import javafx.scene.transform.Rotate; -import javafx.util.Duration; - -import java.util.ArrayList; - - -/** - * @author Shadi Shaheen - * - */ -class JFXCustomColorPicker extends Pane { - - ObjectProperty selectedPath = new SimpleObjectProperty<>(); - private MoveTo startPoint; - private CubicCurveTo curve0To; - private CubicCurveTo outerCircleCurveTo; - private CubicCurveTo curve1To; - private CubicCurveTo innerCircleCurveTo; - private ArrayList curves = new ArrayList<>(); - - private double distance=200; - private double centerX = distance; - private double centerY = distance; - private double radius = 110; - - private int shapesNumber = 13; - private ArrayList shapes = new ArrayList<>(); - private CachedTransition showAnimation; - private JFXColorPickerUI hslColorPicker; - - public JFXCustomColorPicker(){ - this.setPickOnBounds(false); - this.setMinSize(distance*2, distance*2); - - DoubleProperty rotationAngle = new SimpleDoubleProperty(2.1); - - // draw recent colors shape using cubic curves - init(rotationAngle, centerX + 53 ,centerY + 162); - - hslColorPicker = new JFXColorPickerUI((int) distance); - hslColorPicker.setLayoutX(centerX - distance/2); - hslColorPicker.setLayoutY(centerY - distance/2); - this.getChildren().add(hslColorPicker); - - // add recent colors shapes - int shapesStartIndex = this.getChildren().size(); - int shapesEndIndex = shapesStartIndex + shapesNumber; - for (int i = 0 ; i < shapesNumber; i++) { - double angle = 2 * i * Math.PI / shapesNumber ; - RecentColorPath path = new RecentColorPath(startPoint, curve0To, outerCircleCurveTo, curve1To, innerCircleCurveTo); - shapes.add(path); - path.setPickOnBounds(false); - Rotate rotate = new Rotate(Math.toDegrees(angle), centerX, centerY); - path.getTransforms().add(rotate); - this.getChildren().add(shapesStartIndex, path); - path.setFill(Color.valueOf(getDefaultColor(i))); - path.addEventHandler(MouseEvent.MOUSE_CLICKED, (event)-> selectedPath.set(path)); - } - - // add selection listeners - selectedPath.addListener((o,oldVal,newVal)->{ - if(oldVal!=null){ - hslColorPicker.removeColorSelectionNode(oldVal); - oldVal.playTransition(-1); - } - // re-arrange children - while(this.getChildren().indexOf(newVal) != shapesEndIndex - 1){ - Node temp = this.getChildren().get(shapesEndIndex-1); - this.getChildren().remove(shapesEndIndex-1); - this.getChildren().add(shapesStartIndex, temp); - } - // update path fill according to the color picker - newVal.setStroke(Color.rgb(255, 255, 255, 0.87)); - newVal.playTransition(1); - hslColorPicker.moveToColor((Color) newVal.getFill()); - hslColorPicker.addColorSelectionNode(newVal); - }); - // init selection - selectedPath.set((RecentColorPath) this.getChildren().get(shapesStartIndex)); - - - // JFXSlider slider = new JFXSlider(-Math.PI, Math.PI, 2.10); - // slider.setIndicatorPosition(IndicatorPosition.RIGHT); - // rotationAngle.bind(slider.valueProperty()); - // - // VBox info = new VBox(); - // Label startX = new Label(); - // startX.textProperty().bind(Bindings.createStringBinding(()->"StartX : " + curves.get(0).getControlX1(), curves.get(0).controlX1Property())); - // Label startY = new Label(); - // startY.textProperty().bind(Bindings.createStringBinding(()->"startY : " + curves.get(0).getControlY1(), curves.get(0).controlY1Property())); - // Label endX = new Label(); - // endX.textProperty().bind(Bindings.createStringBinding(()->"endX : " + curves.get(0).getControlX2(), curves.get(0).controlX2Property())); - // Label endY = new Label(); - // endY.textProperty().bind(Bindings.createStringBinding(()->"endY : " + curves.get(0).getControlY2(), curves.get(0).controlY2Property())); - // Label rotation = new Label(); - // rotation.textProperty().bind(Bindings.createStringBinding(()->"rotation : " + rotationAngle.get(), rotationAngle)); - // info.getChildren().add(startX); - // info.getChildren().add(startY); - // info.getChildren().add(endX); - // info.getChildren().add(endY); - // info.getChildren().add(rotation); - // - // VBox container = new VBox(); - // container.getChildren().add(info); - // container.getChildren().add(slider); - // container.getChildren().add(pane); - - - // Line controlLine1 = new BoundLine(curves.get(0).controlX1Property(), curves.get(0).controlY1Property(), curves.get(0).startXProperty(), curves.get(0).startYProperty()); - // Line controlLine2 = new BoundLine(curves.get(0).controlX2Property(), curves.get(0).controlY2Property(), curves.get(0).endXProperty(), curves.get(0).endYProperty()); - // - // Anchor control1 = new Anchor(Color.GOLD, curves.get(0).controlX1Property(), curves.get(0).controlY1Property()); - // Anchor control2 = new Anchor(Color.GOLDENROD, curves.get(0).controlX2Property(), curves.get(0).controlY2Property()); - // Anchor start = new Anchor(Color.PALEGREEN, curves.get(0).startXProperty(), curves.get(0).startYProperty()); - // Anchor end = new Anchor(Color.TOMATO, curves.get(0).endXProperty(), curves.get(0).endYProperty()); - // pane.getChildren().addAll(control1); - - // curves.get(0).setControlX1(curves.get(0).getControlX2()); - // curves.get(0).setControlY1(curves.get(0).getControlY2()); - // rotationAngle.set(0); - // - // new Timeline(new KeyFrame(Duration.millis(2000), - // new KeyValue(curves.get(0).controlX1Property(), x, Interpolator.EASE_BOTH), - // new KeyValue(curves.get(0).controlY1Property(), y, Interpolator.EASE_BOTH), - // new KeyValue(rotationAngle, 2.1, Interpolator.EASE_BOTH) - // )).play(); - - // for (int i = 0 ; i < numMoons; i++) { - // double angle = 2 * i * Math.PI / numMoons ; - // - // StackPane shapeContainer = new StackPane(); - // shapeContainer.setLayoutX(300); - // shapeContainer.setLayoutY(300); - //// double xOffset = distance * Math.cos(angle); - //// double yOffset = distance * Math.sin(angle); - //// final double startx = centerX + xOffset ; - //// final double starty = centerY + yOffset ; - // - // shapeContainer.setShape(path); - //// shapeContainer.maxWidthProperty().bind(Bindings.createDoubleBinding(()-> path.getLayoutBounds().getWidth(), path.layoutBoundsProperty())); - //// shapeContainer.maxHeightProperty().bind(Bindings.createDoubleBinding(()-> path.getLayoutBounds().getHeight(), path.layoutBoundsProperty())); - // shapeContainer.minWidthProperty().bind(Bindings.createDoubleBinding(()-> path.getLayoutBounds().getWidth(), path.layoutBoundsProperty())); - // shapeContainer.minHeightProperty().bind(Bindings.createDoubleBinding(()-> path.getLayoutBounds().getHeight(), path.layoutBoundsProperty())); - // JFXDepthManager.setDepth(shapeContainer, 1); - // - // Rotate rotate = new Rotate(Math.toDegrees(angle), 45, 0); - // shapeContainer.getTransforms().add(rotate); - // shapeContainer.widthProperty().addListener((o,oldVal,newVal)->{ - // ((Rotate)shapeContainer.getTransforms().get(0)).setPivotX(newVal.doubleValue()/5.4653); - // }); - // - // colorPicker.getChildren().add(shapeContainer); - // - // switch (i) { - // case 0: - // shapeContainer.setStyle("-fx-background-color:#8F3F7E"); - // break; - // case 1: - // shapeContainer.setStyle("-fx-background-color:#B5305F"); - // break; - // case 2: - // shapeContainer.setStyle("-fx-background-color:#CE584A"); - // break; - // case 3: - // shapeContainer.setStyle("-fx-background-color:#DB8D5C"); - // break; - // case 4: - // shapeContainer.setStyle("-fx-background-color:#DA854E;"); - // break; - // case 5: - // shapeContainer.setStyle("-fx-background-color:#E9AB44;"); - // break; - // case 6: - // shapeContainer.setStyle("-fx-background-color:#FEE435"); - // break; - // case 7: - // shapeContainer.setStyle("-fx-background-color:#99C286"); - // break; - // case 8: - // shapeContainer.setStyle("-fx-background-color:#01A05E"); - // break; - // case 9: - // shapeContainer.setStyle("-fx-background-color:#4A8895"); - // break; - // case 10: - // shapeContainer.setStyle("-fx-background-color:#16669B"); - // break; - // case 11: - // shapeContainer.setStyle("-fx-background-color:#2F65A5"); - // break; - // case 12: - // shapeContainer.setStyle("-fx-background-color:#4E6A9C"); - // break; - // default: - // break; - // } - //// - //// if(i > 0 ){ - //// rotate.pivotXProperty().bind(((Rotate)colorPicker.getChildren().get(1).getTransforms().get(0)).pivotXProperty()); - //// rotate.pivotYProperty().bind(((Rotate)colorPicker.getChildren().get(1).getTransforms().get(0)).pivotYProperty()); - //// shapeContainer.setMouseTransparent(true); - //// }else if( i == 0){ - //// shapeContainer.setStyle("-fx-background-color:blue; -fx-border-color:RED;"); - //// shapeContainer.setOnMouseMoved((move)->{ - //// System.out.println(move.getX() + " , " + move.getY()); - //// rotate.setPivotX(move.getX()); - //// rotate.setPivotY(move.getY()); - //// }); - //// } - //// - // - // - // - // - // - // - // - // } - - // container.getChildren().add(colorPicker); - // VBox.setMargin(colorPicker, new Insets(250,0,0,250)); - - // stage.setTitle("Cubic Curve Manipulation Sample"); - // stage.setScene(new Scene(pane, 700, 700, Color.ALICEBLUE)); - // stage.show(); - - // ScenicView.show(stage.getScene()); - - } - - - public int getShapesNumber(){ - return shapesNumber; - } - - public int getSelectedIndex(){ - if(selectedPath.get()!=null) - return shapes.indexOf(selectedPath.get()); - return -1; - } - - public void setColor(Color color){ - shapes.get(getSelectedIndex()).setFill(color); - hslColorPicker.moveToColor(color); - } - - public Color getColor(int index){ - if(index < shapes.size() && index >= 0) return (Color) shapes.get(index).getFill(); - return Color.WHITE; - } - - - public void preAnimate(){ - double x = curves.get(0).getStartX(); - double y = curves.get(0).getStartY(); - curves.get(0).setStartX(centerX); - curves.get(0).setStartY(centerY); - - double x1 = curves.get(1).getStartX(); - double y1 = curves.get(1).getStartY(); - curves.get(1).setStartX(centerX); - curves.get(1).setStartY(centerY); - - double cx1 = curves.get(0).getControlX1(); - double cy1 = curves.get(0).getControlY1(); - curves.get(0).setControlX1(centerX + radius); - curves.get(0).setControlY1(centerY + radius/2); - - showAnimation = new CachedTransition(this, new Timeline(new KeyFrame(Duration.millis(1000), - new KeyValue(curves.get(0).startXProperty(),x, Interpolator.EASE_BOTH), - new KeyValue(curves.get(0).startYProperty(),y, Interpolator.EASE_BOTH), - new KeyValue(curves.get(1).startXProperty(),x1, Interpolator.EASE_BOTH), - new KeyValue(curves.get(1).startYProperty(),y1, Interpolator.EASE_BOTH), - new KeyValue(curves.get(0).controlX1Property(),cx1, Interpolator.EASE_BOTH), - new KeyValue(curves.get(0).controlY1Property(),cy1, Interpolator.EASE_BOTH) - ))){{ - setCycleDuration(Duration.millis(240)); - setDelay(Duration.millis(0)); - }}; - } - - public void animate() { - showAnimation.play(); - } - - private void init(DoubleProperty rotationAngle, double initControlX1, double initControlY1){ - - Circle innerCircle = new Circle(centerX, centerY, radius, Color.TRANSPARENT); - Circle outerCircle = new Circle(centerX, centerY, radius*2, Color.web("blue", 0.5)); - - // Create a composite shape of 4 cubic curves - // create 2 cubic curves of the shape - createQuadraticCurve(rotationAngle, initControlX1 ,initControlY1); - - // inner circle curve - CubicCurve innerCircleCurve = new CubicCurve(); - innerCircleCurve.startXProperty().bind(curves.get(0).startXProperty()); - innerCircleCurve.startYProperty().bind(curves.get(0).startYProperty()); - innerCircleCurve.endXProperty().bind(curves.get(1).startXProperty()); - innerCircleCurve.endYProperty().bind(curves.get(1).startYProperty()); - curves.get(0).startXProperty().addListener((o,oldVal,newVal)->{ - Point2D controlPoint = makeControlPoint(newVal.doubleValue(), curves.get(0).getStartY(), innerCircle, shapesNumber, -1); - innerCircleCurve.setControlX1(controlPoint.getX()); - innerCircleCurve.setControlY1(controlPoint.getY()); - }); - curves.get(0).startYProperty().addListener((o,oldVal,newVal)->{ - Point2D controlPoint = makeControlPoint(curves.get(0).getStartX(), newVal.doubleValue(), innerCircle, shapesNumber, -1); - innerCircleCurve.setControlX1(controlPoint.getX()); - innerCircleCurve.setControlY1(controlPoint.getY()); - }); - curves.get(1).startXProperty().addListener((o,oldVal,newVal)->{ - Point2D controlPoint = makeControlPoint(newVal.doubleValue(), curves.get(1).getStartY(), innerCircle, shapesNumber, 1); - innerCircleCurve.setControlX2(controlPoint.getX()); - innerCircleCurve.setControlY2(controlPoint.getY()); - }); - curves.get(1).startYProperty().addListener((o,oldVal,newVal)->{ - Point2D controlPoint = makeControlPoint(curves.get(1).getStartX(), newVal.doubleValue(), innerCircle, shapesNumber, 1); - innerCircleCurve.setControlX2(controlPoint.getX()); - innerCircleCurve.setControlY2(controlPoint.getY()); - }); - Point2D controlPoint = makeControlPoint(curves.get(0).getStartX(), curves.get(0).getStartY(), innerCircle, shapesNumber, -1); - innerCircleCurve.setControlX1(controlPoint.getX()); - innerCircleCurve.setControlY1(controlPoint.getY()); - controlPoint = makeControlPoint(curves.get(1).getStartX(), curves.get(1).getStartY(), innerCircle, shapesNumber, 1); - innerCircleCurve.setControlX2(controlPoint.getX()); - innerCircleCurve.setControlY2(controlPoint.getY()); - // innerCircleCurve.setStroke(Color.FORESTGREEN); - // innerCircleCurve.setStrokeWidth(1); - // innerCircleCurve.setStrokeLineCap(StrokeLineCap.ROUND); - // innerCircleCurve.setFill(Color.TRANSPARENT); - // innerCircleCurve.setMouseTransparent(true); - // pane.getChildren().add(new Group( innerCircleCurve)); - - // outter circle curve - CubicCurve outerCircleCurve = new CubicCurve(); - outerCircleCurve.startXProperty().bind(curves.get(0).endXProperty()); - outerCircleCurve.startYProperty().bind(curves.get(0).endYProperty()); - outerCircleCurve.endXProperty().bind(curves.get(1).endXProperty()); - outerCircleCurve.endYProperty().bind(curves.get(1).endYProperty()); - controlPoint = makeControlPoint(curves.get(0).getEndX(), curves.get(0).getEndY(), outerCircle, shapesNumber, -1); - outerCircleCurve.setControlX1(controlPoint.getX()); - outerCircleCurve.setControlY1(controlPoint.getY()); - controlPoint = makeControlPoint(curves.get(1).getEndX(), curves.get(1).getEndY(), outerCircle, shapesNumber, 1); - outerCircleCurve.setControlX2(controlPoint.getX()); - outerCircleCurve.setControlY2(controlPoint.getY()); - // outerCircleCurve.setStroke(Color.FORESTGREEN); - // outerCircleCurve.setStrokeWidth(1); - // outerCircleCurve.setStrokeLineCap(StrokeLineCap.ROUND); - // outerCircleCurve.setFill(Color.TRANSPARENT); - // outerCircleCurve.setMouseTransparent(true); - // pane.getChildren().add(new Group(outerCircleCurve)); - - - - startPoint = new MoveTo(); - startPoint.xProperty().bind(curves.get(0).startXProperty()); - startPoint.yProperty().bind(curves.get(0).startYProperty()); - - curve0To = new CubicCurveTo(); - curve0To.controlX1Property().bind(curves.get(0).controlX1Property()); - curve0To.controlY1Property().bind(curves.get(0).controlY1Property()); - curve0To.controlX2Property().bind(curves.get(0).controlX2Property()); - curve0To.controlY2Property().bind(curves.get(0).controlY2Property()); - curve0To.xProperty().bind(curves.get(0).endXProperty()); - curve0To.yProperty().bind(curves.get(0).endYProperty()); - - outerCircleCurveTo = new CubicCurveTo(); - outerCircleCurveTo.controlX1Property().bind(outerCircleCurve.controlX1Property()); - outerCircleCurveTo.controlY1Property().bind(outerCircleCurve.controlY1Property()); - outerCircleCurveTo.controlX2Property().bind(outerCircleCurve.controlX2Property()); - outerCircleCurveTo.controlY2Property().bind(outerCircleCurve.controlY2Property()); - outerCircleCurveTo.xProperty().bind(outerCircleCurve.endXProperty()); - outerCircleCurveTo.yProperty().bind(outerCircleCurve.endYProperty()); - - curve1To = new CubicCurveTo(); - curve1To.controlX1Property().bind(curves.get(1).controlX2Property()); - curve1To.controlY1Property().bind(curves.get(1).controlY2Property()); - curve1To.controlX2Property().bind(curves.get(1).controlX1Property()); - curve1To.controlY2Property().bind(curves.get(1).controlY1Property()); - curve1To.xProperty().bind(curves.get(1).startXProperty()); - curve1To.yProperty().bind(curves.get(1).startYProperty()); - - innerCircleCurveTo = new CubicCurveTo(); - innerCircleCurveTo.controlX1Property().bind(innerCircleCurve.controlX2Property()); - innerCircleCurveTo.controlY1Property().bind(innerCircleCurve.controlY2Property()); - innerCircleCurveTo.controlX2Property().bind(innerCircleCurve.controlX1Property()); - innerCircleCurveTo.controlY2Property().bind(innerCircleCurve.controlY1Property()); - innerCircleCurveTo.xProperty().bind(innerCircleCurve.startXProperty()); - innerCircleCurveTo.yProperty().bind(innerCircleCurve.startYProperty()); - } - - - private void createQuadraticCurve(DoubleProperty rotationAngle, double initControlX1, double initControlY1) { - - for (int i = 0 ; i < 2; i++) { - - double angle = 2 * i * Math.PI / shapesNumber ; - double xOffset = radius * Math.cos(angle); - double yOffset = radius * Math.sin(angle); - final double startx = centerX + xOffset ; - final double starty = centerY + yOffset ; - - double startXR = Math.cos(rotationAngle.get()) * (startx - centerX) - Math.sin(rotationAngle.get())*(starty-centerY) + centerX; - double startYR = Math.sin(rotationAngle.get()) * (startx - centerX) + Math.cos(rotationAngle.get())*(starty-centerY) + centerY; - - angle = 2 * i * Math.PI / shapesNumber ; - xOffset = distance * Math.cos(angle); - yOffset = distance * Math.sin(angle); - - double endx = centerX + xOffset ; - double endy = centerY + yOffset ; - - CubicCurve curvedLine = new CubicCurve(); - curvedLine.setStartX(startXR); - curvedLine.setStartY(startYR); - curvedLine.setControlX1(startXR); - curvedLine.setControlY1(startYR); - curvedLine.setControlX2(endx); - curvedLine.setControlY2(endy); - curvedLine.setEndX(endx); - curvedLine.setEndY(endy); - curvedLine.setStroke(Color.FORESTGREEN); - curvedLine.setStrokeWidth(1); - curvedLine.setStrokeLineCap(StrokeLineCap.ROUND); - curvedLine.setFill(Color.TRANSPARENT); - curvedLine.setMouseTransparent(true); - rotationAngle.addListener((o,oldVal,newVal)->{ - double newstartXR = Math.cos(rotationAngle.get()) * (startx - centerX) - Math.sin(rotationAngle.get())*(starty-centerY) + centerX; - double newstartYR = Math.sin(rotationAngle.get()) * (startx - centerX) + Math.cos(rotationAngle.get())*(starty-centerY) + centerY; - curvedLine.setStartX(newstartXR); - curvedLine.setStartY(newstartYR); - }); - - curves.add(curvedLine); - - if(i == 0){ - curvedLine.setControlX1(initControlX1); - curvedLine.setControlY1(initControlY1); - }else{ - curvedLine.controlX1Property().bind(Bindings.createDoubleBinding(()->{ - double curveTeta = 2 * curves.indexOf(curvedLine) * Math.PI / shapesNumber; - return Math.cos(curveTeta) * (curves.get(0).getControlX1() - centerX) - Math.sin(curveTeta)*(curves.get(0).getControlY1()-centerY) + centerX; - }, curves.get(0).controlX1Property(), curves.get(0).controlY1Property())); - - curvedLine.controlY1Property().bind(Bindings.createDoubleBinding(()->{ - double curveTeta = 2 * curves.indexOf(curvedLine) * Math.PI / shapesNumber; - return Math.sin(curveTeta) * (curves.get(0).getControlX1() - centerX) + Math.cos(curveTeta)*(curves.get(0).getControlY1()-centerY) + centerY; - }, curves.get(0).controlX1Property(), curves.get(0).controlY1Property())); - - - curvedLine.controlX2Property().bind(Bindings.createDoubleBinding(()->{ - double curveTeta = 2 * curves.indexOf(curvedLine) * Math.PI / shapesNumber; - return Math.cos(curveTeta) * (curves.get(0).getControlX2() - centerX) - Math.sin(curveTeta)*(curves.get(0).getControlY2()-centerY) + centerX; - }, curves.get(0).controlX2Property(), curves.get(0).controlY2Property())); - - curvedLine.controlY2Property().bind(Bindings.createDoubleBinding(()->{ - double curveTeta = 2 * curves.indexOf(curvedLine) * Math.PI / shapesNumber; - return Math.sin(curveTeta) * (curves.get(0).getControlX2() - centerX) + Math.cos(curveTeta)*(curves.get(0).getControlY2()-centerY) + centerY; - }, curves.get(0).controlX2Property(), curves.get(0).controlY2Property())); - } - } - } - - private String getDefaultColor(int i) { - String color = "#FFFFFF"; - switch (i) { - case 0: - color = "#8F3F7E"; - break; - case 1: - color = "#B5305F"; - break; - case 2: - color = "#CE584A"; - break; - case 3: - color = "#DB8D5C"; - break; - case 4: - color = "#DA854E"; - break; - case 5: - color = "#E9AB44"; - break; - case 6: - color = "#FEE435"; - break; - case 7: - color = "#99C286"; - break; - case 8: - color = "#01A05E"; - break; - case 9: - color = "#4A8895"; - break; - case 10: - color = "#16669B"; - break; - case 11: - color = "#2F65A5"; - break; - case 12: - color = "#4E6A9C"; - break; - default: - break; - } - return color; - } - - - // class BoundLine extends Line { - // BoundLine(DoubleProperty startX, DoubleProperty startY, DoubleProperty endX, DoubleProperty endY) { - // startXProperty().bind(startX); - // startYProperty().bind(startY); - // endXProperty().bind(endX); - // endYProperty().bind(endY); - // setStrokeWidth(2); - // setStroke(Color.GRAY.deriveColor(0, 1, 1, 0.5)); - // setStrokeLineCap(StrokeLineCap.BUTT); - // getStrokeDashArray().setAll(10.0, 5.0); - // } - // } - - // a draggable anchor displayed around a point. - // class Anchor extends Circle { - // Anchor(Color color, DoubleProperty x, DoubleProperty y) { - // super(x.get(), y.get(), 10); - // setFill(color.deriveColor(1, 1, 1, 0.5)); - // setStroke(color); - // setStrokeWidth(2); - // setStrokeType(StrokeType.OUTSIDE); - // - // x.bind(centerXProperty()); - // y.bind(centerYProperty()); - // enableDrag(); - // } - // - // // make a node movable by dragging it around with the mouse. - // private void enableDrag() { - // final Delta dragDelta = new Delta(); - // setOnMousePressed(new EventHandler() { - // @Override public void handle(MouseEvent mouseEvent) { - // // record a delta distance for the drag and drop operation. - // dragDelta.x = getCenterX() - mouseEvent.getX(); - // dragDelta.y = getCenterY() - mouseEvent.getY(); - // getScene().setCursor(Cursor.MOVE); - // } - // }); - // setOnMouseReleased(new EventHandler() { - // @Override public void handle(MouseEvent mouseEvent) { - // getScene().setCursor(Cursor.HAND); - // } - // }); - // setOnMouseDragged(new EventHandler() { - // @Override public void handle(MouseEvent mouseEvent) { - // double newX = mouseEvent.getX() + dragDelta.x; - // if (newX > 0 && newX < getScene().getWidth()) { - // setCenterX(newX); - // } - // double newY = mouseEvent.getY() + dragDelta.y; - // if (newY > 0 && newY < getScene().getHeight()) { - // setCenterY(newY); - // } - // } - // }); - // setOnMouseEntered(new EventHandler() { - // @Override public void handle(MouseEvent mouseEvent) { - // if (!mouseEvent.isPrimaryButtonDown()) { - // getScene().setCursor(Cursor.HAND); - // } - // } - // }); - // setOnMouseExited(new EventHandler() { - // @Override public void handle(MouseEvent mouseEvent) { - // if (!mouseEvent.isPrimaryButtonDown()) { - // getScene().setCursor(Cursor.DEFAULT); - // } - // } - // }); - // } - // - // // records relative x and y co-ordinates. - // private class Delta { double x, y; } - // } - - class RecentColorPath extends Path { - PathClickTransition transition; - public RecentColorPath(PathElement... elements) { - super(elements); - this.setStrokeLineCap(StrokeLineCap.ROUND); - this.setStrokeWidth(0); - this.setStrokeType(StrokeType.CENTERED); - this.setCache(true); - JFXDepthManager.setDepth(this, 2); - this.transition = new PathClickTransition(this); - } - public void playTransition(double rate){ - transition.setRate(rate); - transition.play(); - } - } - - private class PathClickTransition extends CachedTransition { - public PathClickTransition(Path path) { - super(JFXCustomColorPicker.this, new Timeline( - new KeyFrame(Duration.ZERO, - new KeyValue(((DropShadow)path.getEffect()).radiusProperty(), JFXDepthManager.getShadowAt(2).radiusProperty().get(), Interpolator.EASE_BOTH), - new KeyValue(((DropShadow)path.getEffect()).spreadProperty(), JFXDepthManager.getShadowAt(2).spreadProperty().get(), Interpolator.EASE_BOTH), - new KeyValue(((DropShadow)path.getEffect()).offsetXProperty(), JFXDepthManager.getShadowAt(2).offsetXProperty().get(), Interpolator.EASE_BOTH), - new KeyValue(((DropShadow)path.getEffect()).offsetYProperty(), JFXDepthManager.getShadowAt(2).offsetYProperty().get(), Interpolator.EASE_BOTH), - new KeyValue(path.strokeWidthProperty(), 0, Interpolator.EASE_BOTH) - ), - new KeyFrame(Duration.millis(1000), - new KeyValue(((DropShadow)path.getEffect()).radiusProperty(), JFXDepthManager.getShadowAt(5).radiusProperty().get(), Interpolator.EASE_BOTH), - new KeyValue(((DropShadow)path.getEffect()).spreadProperty(), JFXDepthManager.getShadowAt(5).spreadProperty().get(), Interpolator.EASE_BOTH), - new KeyValue(((DropShadow)path.getEffect()).offsetXProperty(), JFXDepthManager.getShadowAt(5).offsetXProperty().get(), Interpolator.EASE_BOTH), - new KeyValue(((DropShadow)path.getEffect()).offsetYProperty(), JFXDepthManager.getShadowAt(5).offsetYProperty().get(), Interpolator.EASE_BOTH), - new KeyValue(path.strokeWidthProperty(), 2, Interpolator.EASE_BOTH) - ) - ) - ); - // reduce the number to increase the shifting , increase number to reduce shifting - setCycleDuration(Duration.millis(120)); - setDelay(Duration.seconds(0)); - setAutoReverse(false); - } - } - - /*************************************************************************** - * * - * Util methods * - * * - **************************************************************************/ - - private Point2D rotate(Point2D a, Point2D center, double angle){ - double resultX = center.getX() + (a.getX() - center.getX())*Math.cos(angle) - (a.getY() - center.getY())*Math.sin(angle); - double resultY = center.getY() + (a.getX() - center.getX())*Math.sin(angle) + (a.getY() - center.getY())*Math.cos(angle); - return new Point2D(resultX,resultY); - } - - private Point2D makeControlPoint(double endX,double endY,Circle circle, int numSegments,int direction){ - double controlPointDistance = (4.0/3.0) * Math.tan(Math.PI / (2*numSegments)) * circle.getRadius(); - Point2D center = new Point2D(circle.getCenterX(),circle.getCenterY()); - Point2D end = new Point2D(endX,endY); - Point2D perp = rotate(center, end, direction*Math.PI/2.); - Point2D diff = perp.subtract(end); - diff = diff.normalize(); - diff = scale(diff, controlPointDistance); - perp = end.add(diff); - return perp; - } - - private Point2D scale(Point2D a, double scale){ - return new Point2D(a.getX()*scale,a.getY()*scale); - } - +/* + * 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.skins; + +import com.jfoenix.effects.JFXDepthManager; +import com.jfoenix.transitions.CachedTransition; +import javafx.animation.Interpolator; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.beans.binding.Bindings; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.geometry.Point2D; +import javafx.scene.Node; +import javafx.scene.effect.DropShadow; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.Pane; +import javafx.scene.paint.Color; +import javafx.scene.shape.*; +import javafx.scene.transform.Rotate; +import javafx.util.Duration; + +import java.util.ArrayList; + + +/** + * @author Shadi Shaheen + * + */ +class JFXCustomColorPicker extends Pane { + + ObjectProperty selectedPath = new SimpleObjectProperty<>(); + private MoveTo startPoint; + private CubicCurveTo curve0To; + private CubicCurveTo outerCircleCurveTo; + private CubicCurveTo curve1To; + private CubicCurveTo innerCircleCurveTo; + private ArrayList curves = new ArrayList<>(); + + private double distance=200; + private double centerX = distance; + private double centerY = distance; + private double radius = 110; + + private int shapesNumber = 13; + private ArrayList shapes = new ArrayList<>(); + private CachedTransition showAnimation; + private JFXColorPickerUI hslColorPicker; + + public JFXCustomColorPicker(){ + this.setPickOnBounds(false); + this.setMinSize(distance*2, distance*2); + + DoubleProperty rotationAngle = new SimpleDoubleProperty(2.1); + + // draw recent colors shape using cubic curves + init(rotationAngle, centerX + 53 ,centerY + 162); + + hslColorPicker = new JFXColorPickerUI((int) distance); + hslColorPicker.setLayoutX(centerX - distance/2); + hslColorPicker.setLayoutY(centerY - distance/2); + this.getChildren().add(hslColorPicker); + + // add recent colors shapes + int shapesStartIndex = this.getChildren().size(); + int shapesEndIndex = shapesStartIndex + shapesNumber; + for (int i = 0 ; i < shapesNumber; i++) { + double angle = 2 * i * Math.PI / shapesNumber ; + RecentColorPath path = new RecentColorPath(startPoint, curve0To, outerCircleCurveTo, curve1To, innerCircleCurveTo); + shapes.add(path); + path.setPickOnBounds(false); + Rotate rotate = new Rotate(Math.toDegrees(angle), centerX, centerY); + path.getTransforms().add(rotate); + this.getChildren().add(shapesStartIndex, path); + path.setFill(Color.valueOf(getDefaultColor(i))); + path.addEventHandler(MouseEvent.MOUSE_CLICKED, (event)-> selectedPath.set(path)); + } + + // add selection listeners + selectedPath.addListener((o,oldVal,newVal)->{ + if(oldVal!=null){ + hslColorPicker.removeColorSelectionNode(oldVal); + oldVal.playTransition(-1); + } + // re-arrange children + while(this.getChildren().indexOf(newVal) != shapesEndIndex - 1){ + Node temp = this.getChildren().get(shapesEndIndex-1); + this.getChildren().remove(shapesEndIndex-1); + this.getChildren().add(shapesStartIndex, temp); + } + // update path fill according to the color picker + newVal.setStroke(Color.rgb(255, 255, 255, 0.87)); + newVal.playTransition(1); + hslColorPicker.moveToColor((Color) newVal.getFill()); + hslColorPicker.addColorSelectionNode(newVal); + }); + // init selection + selectedPath.set((RecentColorPath) this.getChildren().get(shapesStartIndex)); + + + // JFXSlider slider = new JFXSlider(-Math.PI, Math.PI, 2.10); + // slider.setIndicatorPosition(IndicatorPosition.RIGHT); + // rotationAngle.bind(slider.valueProperty()); + // + // VBox info = new VBox(); + // Label startX = new Label(); + // startX.textProperty().bind(Bindings.createStringBinding(()->"StartX : " + curves.get(0).getControlX1(), curves.get(0).controlX1Property())); + // Label startY = new Label(); + // startY.textProperty().bind(Bindings.createStringBinding(()->"startY : " + curves.get(0).getControlY1(), curves.get(0).controlY1Property())); + // Label endX = new Label(); + // endX.textProperty().bind(Bindings.createStringBinding(()->"endX : " + curves.get(0).getControlX2(), curves.get(0).controlX2Property())); + // Label endY = new Label(); + // endY.textProperty().bind(Bindings.createStringBinding(()->"endY : " + curves.get(0).getControlY2(), curves.get(0).controlY2Property())); + // Label rotation = new Label(); + // rotation.textProperty().bind(Bindings.createStringBinding(()->"rotation : " + rotationAngle.get(), rotationAngle)); + // info.getChildren().add(startX); + // info.getChildren().add(startY); + // info.getChildren().add(endX); + // info.getChildren().add(endY); + // info.getChildren().add(rotation); + // + // VBox container = new VBox(); + // container.getChildren().add(info); + // container.getChildren().add(slider); + // container.getChildren().add(pane); + + + // Line controlLine1 = new BoundLine(curves.get(0).controlX1Property(), curves.get(0).controlY1Property(), curves.get(0).startXProperty(), curves.get(0).startYProperty()); + // Line controlLine2 = new BoundLine(curves.get(0).controlX2Property(), curves.get(0).controlY2Property(), curves.get(0).endXProperty(), curves.get(0).endYProperty()); + // + // Anchor control1 = new Anchor(Color.GOLD, curves.get(0).controlX1Property(), curves.get(0).controlY1Property()); + // Anchor control2 = new Anchor(Color.GOLDENROD, curves.get(0).controlX2Property(), curves.get(0).controlY2Property()); + // Anchor start = new Anchor(Color.PALEGREEN, curves.get(0).startXProperty(), curves.get(0).startYProperty()); + // Anchor end = new Anchor(Color.TOMATO, curves.get(0).endXProperty(), curves.get(0).endYProperty()); + // pane.getChildren().addAll(control1); + + // curves.get(0).setControlX1(curves.get(0).getControlX2()); + // curves.get(0).setControlY1(curves.get(0).getControlY2()); + // rotationAngle.set(0); + // + // new Timeline(new KeyFrame(Duration.millis(2000), + // new KeyValue(curves.get(0).controlX1Property(), x, Interpolator.EASE_BOTH), + // new KeyValue(curves.get(0).controlY1Property(), y, Interpolator.EASE_BOTH), + // new KeyValue(rotationAngle, 2.1, Interpolator.EASE_BOTH) + // )).play(); + + // for (int i = 0 ; i < numMoons; i++) { + // double angle = 2 * i * Math.PI / numMoons ; + // + // StackPane shapeContainer = new StackPane(); + // shapeContainer.setLayoutX(300); + // shapeContainer.setLayoutY(300); + //// double xOffset = distance * Math.cos(angle); + //// double yOffset = distance * Math.sin(angle); + //// final double startx = centerX + xOffset ; + //// final double starty = centerY + yOffset ; + // + // shapeContainer.setShape(path); + //// shapeContainer.maxWidthProperty().bind(Bindings.createDoubleBinding(()-> path.getLayoutBounds().getWidth(), path.layoutBoundsProperty())); + //// shapeContainer.maxHeightProperty().bind(Bindings.createDoubleBinding(()-> path.getLayoutBounds().getHeight(), path.layoutBoundsProperty())); + // shapeContainer.minWidthProperty().bind(Bindings.createDoubleBinding(()-> path.getLayoutBounds().getWidth(), path.layoutBoundsProperty())); + // shapeContainer.minHeightProperty().bind(Bindings.createDoubleBinding(()-> path.getLayoutBounds().getHeight(), path.layoutBoundsProperty())); + // JFXDepthManager.setDepth(shapeContainer, 1); + // + // Rotate rotate = new Rotate(Math.toDegrees(angle), 45, 0); + // shapeContainer.getTransforms().add(rotate); + // shapeContainer.widthProperty().addListener((o,oldVal,newVal)->{ + // ((Rotate)shapeContainer.getTransforms().get(0)).setPivotX(newVal.doubleValue()/5.4653); + // }); + // + // colorPicker.getChildren().add(shapeContainer); + // + // switch (i) { + // case 0: + // shapeContainer.setStyle("-fx-background-color:#8F3F7E"); + // break; + // case 1: + // shapeContainer.setStyle("-fx-background-color:#B5305F"); + // break; + // case 2: + // shapeContainer.setStyle("-fx-background-color:#CE584A"); + // break; + // case 3: + // shapeContainer.setStyle("-fx-background-color:#DB8D5C"); + // break; + // case 4: + // shapeContainer.setStyle("-fx-background-color:#DA854E;"); + // break; + // case 5: + // shapeContainer.setStyle("-fx-background-color:#E9AB44;"); + // break; + // case 6: + // shapeContainer.setStyle("-fx-background-color:#FEE435"); + // break; + // case 7: + // shapeContainer.setStyle("-fx-background-color:#99C286"); + // break; + // case 8: + // shapeContainer.setStyle("-fx-background-color:#01A05E"); + // break; + // case 9: + // shapeContainer.setStyle("-fx-background-color:#4A8895"); + // break; + // case 10: + // shapeContainer.setStyle("-fx-background-color:#16669B"); + // break; + // case 11: + // shapeContainer.setStyle("-fx-background-color:#2F65A5"); + // break; + // case 12: + // shapeContainer.setStyle("-fx-background-color:#4E6A9C"); + // break; + // default: + // break; + // } + //// + //// if(i > 0 ){ + //// rotate.pivotXProperty().bind(((Rotate)colorPicker.getChildren().get(1).getTransforms().get(0)).pivotXProperty()); + //// rotate.pivotYProperty().bind(((Rotate)colorPicker.getChildren().get(1).getTransforms().get(0)).pivotYProperty()); + //// shapeContainer.setMouseTransparent(true); + //// }else if( i == 0){ + //// shapeContainer.setStyle("-fx-background-color:blue; -fx-border-color:RED;"); + //// shapeContainer.setOnMouseMoved((move)->{ + //// System.out.println(move.getX() + " , " + move.getY()); + //// rotate.setPivotX(move.getX()); + //// rotate.setPivotY(move.getY()); + //// }); + //// } + //// + // + // + // + // + // + // + // + // } + + // container.getChildren().add(colorPicker); + // VBox.setMargin(colorPicker, new Insets(250,0,0,250)); + + // stage.setTitle("Cubic Curve Manipulation Sample"); + // stage.setScene(new Scene(pane, 700, 700, Color.ALICEBLUE)); + // stage.show(); + + // ScenicView.show(stage.getScene()); + + } + + + public int getShapesNumber(){ + return shapesNumber; + } + + public int getSelectedIndex(){ + if(selectedPath.get()!=null) + return shapes.indexOf(selectedPath.get()); + return -1; + } + + public void setColor(Color color){ + shapes.get(getSelectedIndex()).setFill(color); + hslColorPicker.moveToColor(color); + } + + public Color getColor(int index){ + if(index < shapes.size() && index >= 0) return (Color) shapes.get(index).getFill(); + return Color.WHITE; + } + + + public void preAnimate(){ + double x = curves.get(0).getStartX(); + double y = curves.get(0).getStartY(); + curves.get(0).setStartX(centerX); + curves.get(0).setStartY(centerY); + + double x1 = curves.get(1).getStartX(); + double y1 = curves.get(1).getStartY(); + curves.get(1).setStartX(centerX); + curves.get(1).setStartY(centerY); + + double cx1 = curves.get(0).getControlX1(); + double cy1 = curves.get(0).getControlY1(); + curves.get(0).setControlX1(centerX + radius); + curves.get(0).setControlY1(centerY + radius/2); + + showAnimation = new CachedTransition(this, new Timeline(new KeyFrame(Duration.millis(1000), + new KeyValue(curves.get(0).startXProperty(),x, Interpolator.EASE_BOTH), + new KeyValue(curves.get(0).startYProperty(),y, Interpolator.EASE_BOTH), + new KeyValue(curves.get(1).startXProperty(),x1, Interpolator.EASE_BOTH), + new KeyValue(curves.get(1).startYProperty(),y1, Interpolator.EASE_BOTH), + new KeyValue(curves.get(0).controlX1Property(),cx1, Interpolator.EASE_BOTH), + new KeyValue(curves.get(0).controlY1Property(),cy1, Interpolator.EASE_BOTH) + ))){{ + setCycleDuration(Duration.millis(240)); + setDelay(Duration.millis(0)); + }}; + } + + public void animate() { + showAnimation.play(); + } + + private void init(DoubleProperty rotationAngle, double initControlX1, double initControlY1){ + + Circle innerCircle = new Circle(centerX, centerY, radius, Color.TRANSPARENT); + Circle outerCircle = new Circle(centerX, centerY, radius*2, Color.web("blue", 0.5)); + + // Create a composite shape of 4 cubic curves + // create 2 cubic curves of the shape + createQuadraticCurve(rotationAngle, initControlX1 ,initControlY1); + + // inner circle curve + CubicCurve innerCircleCurve = new CubicCurve(); + innerCircleCurve.startXProperty().bind(curves.get(0).startXProperty()); + innerCircleCurve.startYProperty().bind(curves.get(0).startYProperty()); + innerCircleCurve.endXProperty().bind(curves.get(1).startXProperty()); + innerCircleCurve.endYProperty().bind(curves.get(1).startYProperty()); + curves.get(0).startXProperty().addListener((o,oldVal,newVal)->{ + Point2D controlPoint = makeControlPoint(newVal.doubleValue(), curves.get(0).getStartY(), innerCircle, shapesNumber, -1); + innerCircleCurve.setControlX1(controlPoint.getX()); + innerCircleCurve.setControlY1(controlPoint.getY()); + }); + curves.get(0).startYProperty().addListener((o,oldVal,newVal)->{ + Point2D controlPoint = makeControlPoint(curves.get(0).getStartX(), newVal.doubleValue(), innerCircle, shapesNumber, -1); + innerCircleCurve.setControlX1(controlPoint.getX()); + innerCircleCurve.setControlY1(controlPoint.getY()); + }); + curves.get(1).startXProperty().addListener((o,oldVal,newVal)->{ + Point2D controlPoint = makeControlPoint(newVal.doubleValue(), curves.get(1).getStartY(), innerCircle, shapesNumber, 1); + innerCircleCurve.setControlX2(controlPoint.getX()); + innerCircleCurve.setControlY2(controlPoint.getY()); + }); + curves.get(1).startYProperty().addListener((o,oldVal,newVal)->{ + Point2D controlPoint = makeControlPoint(curves.get(1).getStartX(), newVal.doubleValue(), innerCircle, shapesNumber, 1); + innerCircleCurve.setControlX2(controlPoint.getX()); + innerCircleCurve.setControlY2(controlPoint.getY()); + }); + Point2D controlPoint = makeControlPoint(curves.get(0).getStartX(), curves.get(0).getStartY(), innerCircle, shapesNumber, -1); + innerCircleCurve.setControlX1(controlPoint.getX()); + innerCircleCurve.setControlY1(controlPoint.getY()); + controlPoint = makeControlPoint(curves.get(1).getStartX(), curves.get(1).getStartY(), innerCircle, shapesNumber, 1); + innerCircleCurve.setControlX2(controlPoint.getX()); + innerCircleCurve.setControlY2(controlPoint.getY()); + // innerCircleCurve.setStroke(Color.FORESTGREEN); + // innerCircleCurve.setStrokeWidth(1); + // innerCircleCurve.setStrokeLineCap(StrokeLineCap.ROUND); + // innerCircleCurve.setFill(Color.TRANSPARENT); + // innerCircleCurve.setMouseTransparent(true); + // pane.getChildren().add(new Group( innerCircleCurve)); + + // outter circle curve + CubicCurve outerCircleCurve = new CubicCurve(); + outerCircleCurve.startXProperty().bind(curves.get(0).endXProperty()); + outerCircleCurve.startYProperty().bind(curves.get(0).endYProperty()); + outerCircleCurve.endXProperty().bind(curves.get(1).endXProperty()); + outerCircleCurve.endYProperty().bind(curves.get(1).endYProperty()); + controlPoint = makeControlPoint(curves.get(0).getEndX(), curves.get(0).getEndY(), outerCircle, shapesNumber, -1); + outerCircleCurve.setControlX1(controlPoint.getX()); + outerCircleCurve.setControlY1(controlPoint.getY()); + controlPoint = makeControlPoint(curves.get(1).getEndX(), curves.get(1).getEndY(), outerCircle, shapesNumber, 1); + outerCircleCurve.setControlX2(controlPoint.getX()); + outerCircleCurve.setControlY2(controlPoint.getY()); + // outerCircleCurve.setStroke(Color.FORESTGREEN); + // outerCircleCurve.setStrokeWidth(1); + // outerCircleCurve.setStrokeLineCap(StrokeLineCap.ROUND); + // outerCircleCurve.setFill(Color.TRANSPARENT); + // outerCircleCurve.setMouseTransparent(true); + // pane.getChildren().add(new Group(outerCircleCurve)); + + + + startPoint = new MoveTo(); + startPoint.xProperty().bind(curves.get(0).startXProperty()); + startPoint.yProperty().bind(curves.get(0).startYProperty()); + + curve0To = new CubicCurveTo(); + curve0To.controlX1Property().bind(curves.get(0).controlX1Property()); + curve0To.controlY1Property().bind(curves.get(0).controlY1Property()); + curve0To.controlX2Property().bind(curves.get(0).controlX2Property()); + curve0To.controlY2Property().bind(curves.get(0).controlY2Property()); + curve0To.xProperty().bind(curves.get(0).endXProperty()); + curve0To.yProperty().bind(curves.get(0).endYProperty()); + + outerCircleCurveTo = new CubicCurveTo(); + outerCircleCurveTo.controlX1Property().bind(outerCircleCurve.controlX1Property()); + outerCircleCurveTo.controlY1Property().bind(outerCircleCurve.controlY1Property()); + outerCircleCurveTo.controlX2Property().bind(outerCircleCurve.controlX2Property()); + outerCircleCurveTo.controlY2Property().bind(outerCircleCurve.controlY2Property()); + outerCircleCurveTo.xProperty().bind(outerCircleCurve.endXProperty()); + outerCircleCurveTo.yProperty().bind(outerCircleCurve.endYProperty()); + + curve1To = new CubicCurveTo(); + curve1To.controlX1Property().bind(curves.get(1).controlX2Property()); + curve1To.controlY1Property().bind(curves.get(1).controlY2Property()); + curve1To.controlX2Property().bind(curves.get(1).controlX1Property()); + curve1To.controlY2Property().bind(curves.get(1).controlY1Property()); + curve1To.xProperty().bind(curves.get(1).startXProperty()); + curve1To.yProperty().bind(curves.get(1).startYProperty()); + + innerCircleCurveTo = new CubicCurveTo(); + innerCircleCurveTo.controlX1Property().bind(innerCircleCurve.controlX2Property()); + innerCircleCurveTo.controlY1Property().bind(innerCircleCurve.controlY2Property()); + innerCircleCurveTo.controlX2Property().bind(innerCircleCurve.controlX1Property()); + innerCircleCurveTo.controlY2Property().bind(innerCircleCurve.controlY1Property()); + innerCircleCurveTo.xProperty().bind(innerCircleCurve.startXProperty()); + innerCircleCurveTo.yProperty().bind(innerCircleCurve.startYProperty()); + } + + + private void createQuadraticCurve(DoubleProperty rotationAngle, double initControlX1, double initControlY1) { + + for (int i = 0 ; i < 2; i++) { + + double angle = 2 * i * Math.PI / shapesNumber ; + double xOffset = radius * Math.cos(angle); + double yOffset = radius * Math.sin(angle); + final double startx = centerX + xOffset ; + final double starty = centerY + yOffset ; + + double startXR = Math.cos(rotationAngle.get()) * (startx - centerX) - Math.sin(rotationAngle.get())*(starty-centerY) + centerX; + double startYR = Math.sin(rotationAngle.get()) * (startx - centerX) + Math.cos(rotationAngle.get())*(starty-centerY) + centerY; + + angle = 2 * i * Math.PI / shapesNumber ; + xOffset = distance * Math.cos(angle); + yOffset = distance * Math.sin(angle); + + double endx = centerX + xOffset ; + double endy = centerY + yOffset ; + + CubicCurve curvedLine = new CubicCurve(); + curvedLine.setStartX(startXR); + curvedLine.setStartY(startYR); + curvedLine.setControlX1(startXR); + curvedLine.setControlY1(startYR); + curvedLine.setControlX2(endx); + curvedLine.setControlY2(endy); + curvedLine.setEndX(endx); + curvedLine.setEndY(endy); + curvedLine.setStroke(Color.FORESTGREEN); + curvedLine.setStrokeWidth(1); + curvedLine.setStrokeLineCap(StrokeLineCap.ROUND); + curvedLine.setFill(Color.TRANSPARENT); + curvedLine.setMouseTransparent(true); + rotationAngle.addListener((o,oldVal,newVal)->{ + double newstartXR = Math.cos(rotationAngle.get()) * (startx - centerX) - Math.sin(rotationAngle.get())*(starty-centerY) + centerX; + double newstartYR = Math.sin(rotationAngle.get()) * (startx - centerX) + Math.cos(rotationAngle.get())*(starty-centerY) + centerY; + curvedLine.setStartX(newstartXR); + curvedLine.setStartY(newstartYR); + }); + + curves.add(curvedLine); + + if(i == 0){ + curvedLine.setControlX1(initControlX1); + curvedLine.setControlY1(initControlY1); + }else{ + curvedLine.controlX1Property().bind(Bindings.createDoubleBinding(()->{ + double curveTeta = 2 * curves.indexOf(curvedLine) * Math.PI / shapesNumber; + return Math.cos(curveTeta) * (curves.get(0).getControlX1() - centerX) - Math.sin(curveTeta)*(curves.get(0).getControlY1()-centerY) + centerX; + }, curves.get(0).controlX1Property(), curves.get(0).controlY1Property())); + + curvedLine.controlY1Property().bind(Bindings.createDoubleBinding(()->{ + double curveTeta = 2 * curves.indexOf(curvedLine) * Math.PI / shapesNumber; + return Math.sin(curveTeta) * (curves.get(0).getControlX1() - centerX) + Math.cos(curveTeta)*(curves.get(0).getControlY1()-centerY) + centerY; + }, curves.get(0).controlX1Property(), curves.get(0).controlY1Property())); + + + curvedLine.controlX2Property().bind(Bindings.createDoubleBinding(()->{ + double curveTeta = 2 * curves.indexOf(curvedLine) * Math.PI / shapesNumber; + return Math.cos(curveTeta) * (curves.get(0).getControlX2() - centerX) - Math.sin(curveTeta)*(curves.get(0).getControlY2()-centerY) + centerX; + }, curves.get(0).controlX2Property(), curves.get(0).controlY2Property())); + + curvedLine.controlY2Property().bind(Bindings.createDoubleBinding(()->{ + double curveTeta = 2 * curves.indexOf(curvedLine) * Math.PI / shapesNumber; + return Math.sin(curveTeta) * (curves.get(0).getControlX2() - centerX) + Math.cos(curveTeta)*(curves.get(0).getControlY2()-centerY) + centerY; + }, curves.get(0).controlX2Property(), curves.get(0).controlY2Property())); + } + } + } + + private String getDefaultColor(int i) { + String color = "#FFFFFF"; + switch (i) { + case 0: + color = "#8F3F7E"; + break; + case 1: + color = "#B5305F"; + break; + case 2: + color = "#CE584A"; + break; + case 3: + color = "#DB8D5C"; + break; + case 4: + color = "#DA854E"; + break; + case 5: + color = "#E9AB44"; + break; + case 6: + color = "#FEE435"; + break; + case 7: + color = "#99C286"; + break; + case 8: + color = "#01A05E"; + break; + case 9: + color = "#4A8895"; + break; + case 10: + color = "#16669B"; + break; + case 11: + color = "#2F65A5"; + break; + case 12: + color = "#4E6A9C"; + break; + default: + break; + } + return color; + } + + + // class BoundLine extends Line { + // BoundLine(DoubleProperty startX, DoubleProperty startY, DoubleProperty endX, DoubleProperty endY) { + // startXProperty().bind(startX); + // startYProperty().bind(startY); + // endXProperty().bind(endX); + // endYProperty().bind(endY); + // setStrokeWidth(2); + // setStroke(Color.GRAY.deriveColor(0, 1, 1, 0.5)); + // setStrokeLineCap(StrokeLineCap.BUTT); + // getStrokeDashArray().setAll(10.0, 5.0); + // } + // } + + // a draggable anchor displayed around a point. + // class Anchor extends Circle { + // Anchor(Color color, DoubleProperty x, DoubleProperty y) { + // super(x.get(), y.get(), 10); + // setFill(color.deriveColor(1, 1, 1, 0.5)); + // setStroke(color); + // setStrokeWidth(2); + // setStrokeType(StrokeType.OUTSIDE); + // + // x.bind(centerXProperty()); + // y.bind(centerYProperty()); + // enableDrag(); + // } + // + // // make a node movable by dragging it around with the mouse. + // private void enableDrag() { + // final Delta dragDelta = new Delta(); + // setOnMousePressed(new EventHandler() { + // @Override public void handle(MouseEvent mouseEvent) { + // // record a delta distance for the drag and drop operation. + // dragDelta.x = getCenterX() - mouseEvent.getX(); + // dragDelta.y = getCenterY() - mouseEvent.getY(); + // getScene().setCursor(Cursor.MOVE); + // } + // }); + // setOnMouseReleased(new EventHandler() { + // @Override public void handle(MouseEvent mouseEvent) { + // getScene().setCursor(Cursor.HAND); + // } + // }); + // setOnMouseDragged(new EventHandler() { + // @Override public void handle(MouseEvent mouseEvent) { + // double newX = mouseEvent.getX() + dragDelta.x; + // if (newX > 0 && newX < getScene().getWidth()) { + // setCenterX(newX); + // } + // double newY = mouseEvent.getY() + dragDelta.y; + // if (newY > 0 && newY < getScene().getHeight()) { + // setCenterY(newY); + // } + // } + // }); + // setOnMouseEntered(new EventHandler() { + // @Override public void handle(MouseEvent mouseEvent) { + // if (!mouseEvent.isPrimaryButtonDown()) { + // getScene().setCursor(Cursor.HAND); + // } + // } + // }); + // setOnMouseExited(new EventHandler() { + // @Override public void handle(MouseEvent mouseEvent) { + // if (!mouseEvent.isPrimaryButtonDown()) { + // getScene().setCursor(Cursor.DEFAULT); + // } + // } + // }); + // } + // + // // records relative x and y co-ordinates. + // private class Delta { double x, y; } + // } + + class RecentColorPath extends Path { + PathClickTransition transition; + public RecentColorPath(PathElement... elements) { + super(elements); + this.setStrokeLineCap(StrokeLineCap.ROUND); + this.setStrokeWidth(0); + this.setStrokeType(StrokeType.CENTERED); + this.setCache(true); + JFXDepthManager.setDepth(this, 2); + this.transition = new PathClickTransition(this); + } + public void playTransition(double rate){ + transition.setRate(rate); + transition.play(); + } + } + + private class PathClickTransition extends CachedTransition { + public PathClickTransition(Path path) { + super(JFXCustomColorPicker.this, new Timeline( + new KeyFrame(Duration.ZERO, + new KeyValue(((DropShadow)path.getEffect()).radiusProperty(), JFXDepthManager.getShadowAt(2).radiusProperty().get(), Interpolator.EASE_BOTH), + new KeyValue(((DropShadow)path.getEffect()).spreadProperty(), JFXDepthManager.getShadowAt(2).spreadProperty().get(), Interpolator.EASE_BOTH), + new KeyValue(((DropShadow)path.getEffect()).offsetXProperty(), JFXDepthManager.getShadowAt(2).offsetXProperty().get(), Interpolator.EASE_BOTH), + new KeyValue(((DropShadow)path.getEffect()).offsetYProperty(), JFXDepthManager.getShadowAt(2).offsetYProperty().get(), Interpolator.EASE_BOTH), + new KeyValue(path.strokeWidthProperty(), 0, Interpolator.EASE_BOTH) + ), + new KeyFrame(Duration.millis(1000), + new KeyValue(((DropShadow)path.getEffect()).radiusProperty(), JFXDepthManager.getShadowAt(5).radiusProperty().get(), Interpolator.EASE_BOTH), + new KeyValue(((DropShadow)path.getEffect()).spreadProperty(), JFXDepthManager.getShadowAt(5).spreadProperty().get(), Interpolator.EASE_BOTH), + new KeyValue(((DropShadow)path.getEffect()).offsetXProperty(), JFXDepthManager.getShadowAt(5).offsetXProperty().get(), Interpolator.EASE_BOTH), + new KeyValue(((DropShadow)path.getEffect()).offsetYProperty(), JFXDepthManager.getShadowAt(5).offsetYProperty().get(), Interpolator.EASE_BOTH), + new KeyValue(path.strokeWidthProperty(), 2, Interpolator.EASE_BOTH) + ) + ) + ); + // reduce the number to increase the shifting , increase number to reduce shifting + setCycleDuration(Duration.millis(120)); + setDelay(Duration.seconds(0)); + setAutoReverse(false); + } + } + + /*************************************************************************** + * * + * Util methods * + * * + **************************************************************************/ + + private Point2D rotate(Point2D a, Point2D center, double angle){ + double resultX = center.getX() + (a.getX() - center.getX())*Math.cos(angle) - (a.getY() - center.getY())*Math.sin(angle); + double resultY = center.getY() + (a.getX() - center.getX())*Math.sin(angle) + (a.getY() - center.getY())*Math.cos(angle); + return new Point2D(resultX,resultY); + } + + private Point2D makeControlPoint(double endX,double endY,Circle circle, int numSegments,int direction){ + double controlPointDistance = (4.0/3.0) * Math.tan(Math.PI / (2*numSegments)) * circle.getRadius(); + Point2D center = new Point2D(circle.getCenterX(),circle.getCenterY()); + Point2D end = new Point2D(endX,endY); + Point2D perp = rotate(center, end, direction*Math.PI/2.); + Point2D diff = perp.subtract(end); + diff = diff.normalize(); + diff = scale(diff, controlPointDistance); + perp = end.add(diff); + return perp; + } + + private Point2D scale(Point2D a, double scale){ + return new Point2D(a.getX()*scale,a.getY()*scale); + } + } \ No newline at end of file diff --git a/src/com/jfoenix/skins/JFXCustomColorPickerDialog.java b/jfoenix/src/main/java/com/jfoenix/skins/JFXCustomColorPickerDialog.java similarity index 97% rename from src/com/jfoenix/skins/JFXCustomColorPickerDialog.java rename to jfoenix/src/main/java/com/jfoenix/skins/JFXCustomColorPickerDialog.java index 3c587c9b..eb01d8db 100644 --- a/src/com/jfoenix/skins/JFXCustomColorPickerDialog.java +++ b/jfoenix/src/main/java/com/jfoenix/skins/JFXCustomColorPickerDialog.java @@ -1,367 +1,367 @@ -/* - * 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.skins; - -import com.jfoenix.controls.*; -import com.jfoenix.svg.SVGGlyph; -import com.jfoenix.transitions.JFXFillTransition; -import javafx.animation.*; -import javafx.application.Platform; -import javafx.beans.InvalidationListener; -import javafx.beans.Observable; -import javafx.beans.binding.Bindings; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.event.EventHandler; -import javafx.geometry.Insets; -import javafx.geometry.Rectangle2D; -import javafx.scene.Scene; -import javafx.scene.control.Label; -import javafx.scene.control.Tab; -import javafx.scene.input.KeyEvent; -import javafx.scene.layout.*; -import javafx.scene.paint.Color; -import javafx.scene.shape.Line; -import javafx.stage.*; -import javafx.util.Duration; - -import java.util.concurrent.atomic.AtomicInteger; - -/** - * @author Shadi Shaheen - * - */ -public class JFXCustomColorPickerDialog extends StackPane { - - private final Stage dialog = new Stage(); - // used for concurrency control and preventing FX-thread over use - private final AtomicInteger concurrencyController = new AtomicInteger(-1); - - private ObjectProperty currentColorProperty = new SimpleObjectProperty<>(Color.WHITE); - private ObjectProperty customColorProperty = new SimpleObjectProperty<>(Color.TRANSPARENT); - private Runnable onSave; - - private Scene customScene; - private JFXCustomColorPicker curvedColorPicker; - private ParallelTransition paraTransition; - private JFXDecorator pickerDecorator; - private boolean systemChange = false; - private boolean userChange = false; - private boolean initOnce = true; - private Runnable initRun; - - public JFXCustomColorPickerDialog(Window owner) { - getStyleClass().add("custom-color-dialog"); - if (owner != null) dialog.initOwner(owner); - dialog.initModality(Modality.APPLICATION_MODAL); - dialog.initStyle(StageStyle.TRANSPARENT); - dialog.setResizable(false); - - // create JFX Decorator - pickerDecorator = new JFXDecorator(dialog,this, false,false,false); - pickerDecorator.setOnCloseButtonAction(()-> close()); - pickerDecorator.setPickOnBounds(false); - // JFXDepthManager.setDepth(pickerDecorator, 2); - // StackPane decoratorContainer = new StackPane(pickerDecorator); - // decoratorContainer.setPadding(new Insets(20)); - // decoratorContainer.setStyle("-fx-background-color:TRANSPARENT;"); - // decoratorContainer.setPickOnBounds(false); - customScene = new Scene(pickerDecorator, Color.TRANSPARENT); - final Scene ownerScene = owner.getScene(); - if (ownerScene != null) { - if (ownerScene.getUserAgentStylesheet() != null) - customScene.setUserAgentStylesheet(ownerScene.getUserAgentStylesheet()); - customScene.getStylesheets().addAll(ownerScene.getStylesheets()); - } - curvedColorPicker = new JFXCustomColorPicker(); - - StackPane pane = new StackPane(curvedColorPicker); - pane.setPadding(new Insets(18)); - - VBox container = new VBox(); - container.getChildren().add(pane); - - JFXTabPane tabs = new JFXTabPane(); - - JFXTextField rgbField = new JFXTextField(); - JFXTextField hsbField = new JFXTextField(); - JFXTextField hexField = new JFXTextField(); - - rgbField.setStyle("-fx-background-color:TRANSPARENT;-fx-font-weight: BOLD;-fx-prompt-text-fill: #808080; -fx-alignment: top-left ; -fx-max-width: 300;"); - rgbField.setPromptText("RGB Color"); - rgbField.textProperty().addListener((o,oldVal,newVal)-> updateColorFromUserInput(newVal)); - - hsbField.setStyle("-fx-background-color:TRANSPARENT;-fx-font-weight: BOLD;-fx-prompt-text-fill: #808080; -fx-alignment: top-left ; -fx-max-width: 300;"); - hsbField.setPromptText("HSB Color"); - hsbField.textProperty().addListener((o,oldVal,newVal)-> updateColorFromUserInput(newVal)); - - hexField.setStyle("-fx-background-color:TRANSPARENT;-fx-font-weight: BOLD;-fx-prompt-text-fill: #808080; -fx-alignment: top-left ; -fx-max-width: 300;"); - hexField.setPromptText("#HEX Color"); - hexField.textProperty().addListener((o,oldVal,newVal)-> updateColorFromUserInput(newVal)); - - StackPane tabContent = new StackPane(); - tabContent.getChildren().add(rgbField); - tabContent.setMinHeight(100); - - Tab rgbTab = new Tab("RGB"); - rgbTab.setContent(tabContent); - Tab hsbTab = new Tab("HSB"); - hsbTab.setContent(hsbField); - Tab hexTab = new Tab("HEX"); - hexTab.setContent(hexField); - - tabs.getTabs().add(rgbTab); - tabs.getTabs().add(hsbTab); - tabs.getTabs().add(hexTab); - - curvedColorPicker.selectedPath.addListener((o,oldVal,newVal)->{ - if(paraTransition!=null) paraTransition.stop(); - Region tabsHeader = (Region) tabs.lookup(".tab-header-background"); - pane.backgroundProperty().unbind(); - tabsHeader.backgroundProperty().unbind(); - JFXFillTransition fillTransition = new JFXFillTransition(Duration.millis(240), pane, (Color)oldVal.getFill(), (Color)newVal.getFill()); - JFXFillTransition tabsFillTransition = new JFXFillTransition(Duration.millis(240), tabsHeader, (Color)oldVal.getFill(), (Color)newVal.getFill()); - paraTransition = new ParallelTransition(fillTransition, tabsFillTransition); - paraTransition.setOnFinished((finish)->{ - tabsHeader.backgroundProperty().bind(Bindings.createObjectBinding(()->{ - return new Background(new BackgroundFill(newVal.getFill(), CornerRadii.EMPTY, Insets.EMPTY)); - }, newVal.fillProperty())); - pane.backgroundProperty().bind(Bindings.createObjectBinding(()->{ - return new Background(new BackgroundFill(newVal.getFill(), CornerRadii.EMPTY, Insets.EMPTY)); - }, newVal.fillProperty())); - }); - paraTransition.play(); - }); - - initRun = ()->{ - // change tabs labels font color according to the selected color - pane.backgroundProperty().addListener((o,oldVal,newVal)->{ - if (concurrencyController.getAndSet(1) == -1) { - Color fontColor = ((Color)newVal.getFills().get(0).getFill()).grayscale().getRed() > 0.5? Color.valueOf("rgba(40, 40, 40, 0.87)") : Color.valueOf("rgba(255, 255, 255, 0.87)"); - tabs.lookupAll(".tab").forEach(tabNode->tabNode.lookupAll(".tab-label").forEach(node-> ((Label)node).setTextFill(fontColor))); - tabs.lookupAll(".tab").forEach(tabNode->tabNode.lookupAll(".jfx-rippler").forEach(node-> ((JFXRippler)node).setRipplerFill(fontColor))); - ((Line)tabs.lookup(".tab-selected-line")).setStroke(fontColor); - pickerDecorator.lookupAll(".jfx-decorator-button").forEach(button->{ - ((JFXButton)button).setRipplerFill(fontColor); - ((SVGGlyph)((JFXButton)button).getGraphic()).setFill(fontColor); - }); - - Color newColor = (Color) newVal.getFills().get(0).getFill(); - String hex = String.format("#%02X%02X%02X", - (int)( newColor.getRed() * 255), - (int)( newColor.getGreen() * 255), - (int)( newColor.getBlue() * 255)); - String rgb = String.format("rgba(%d, %d, %d, 1)", - (int)( newColor.getRed() * 255), - (int)( newColor.getGreen() * 255), - (int)( newColor.getBlue() * 255)); - String hsb = String.format("hsl(%d, %d%%, %d%%)", - (int)( newColor.getHue()), - (int)(newColor.getSaturation()*100), - (int)(newColor.getBrightness()*100)); - - if(!userChange){ - systemChange = true; - rgbField.setText(rgb); - hsbField.setText(hsb); - hexField.setText(hex); - systemChange = false; - } - concurrencyController.getAndSet(-1); - } - }); - - // initial selected colors - Platform.runLater(()->{ - pane.setBackground(new Background(new BackgroundFill(curvedColorPicker.getColor(curvedColorPicker.getSelectedIndex()), CornerRadii.EMPTY, Insets.EMPTY))); - ((Region) tabs.lookup(".tab-header-background")).setBackground(new Background(new BackgroundFill(curvedColorPicker.getColor(curvedColorPicker.getSelectedIndex()), CornerRadii.EMPTY, Insets.EMPTY))); - Region tabsHeader = (Region) tabs.lookup(".tab-header-background"); - pane.backgroundProperty().unbind(); - tabsHeader.backgroundProperty().unbind(); - tabsHeader.backgroundProperty().bind(Bindings.createObjectBinding(()->{ - return new Background(new BackgroundFill(curvedColorPicker.selectedPath.get().getFill(), CornerRadii.EMPTY, Insets.EMPTY)); - }, curvedColorPicker.selectedPath.get().fillProperty())); - pane.backgroundProperty().bind(Bindings.createObjectBinding(()->{ - return new Background(new BackgroundFill(curvedColorPicker.selectedPath.get().getFill(), CornerRadii.EMPTY, Insets.EMPTY)); - }, curvedColorPicker.selectedPath.get().fillProperty())); - - // bind text field line color - rgbField.focusColorProperty().bind(Bindings.createObjectBinding(()->{ - return pane.getBackground().getFills().get(0).getFill(); - }, pane.backgroundProperty())); - hsbField.focusColorProperty().bind(Bindings.createObjectBinding(()->{ - return pane.getBackground().getFills().get(0).getFill(); - }, pane.backgroundProperty())); - hexField.focusColorProperty().bind(Bindings.createObjectBinding(()->{ - return pane.getBackground().getFills().get(0).getFill(); - }, pane.backgroundProperty())); - - - ((Pane)pickerDecorator.lookup(".jfx-decorator-buttons-container")).backgroundProperty().bind(Bindings.createObjectBinding(()->{ - return new Background(new BackgroundFill((Color) pane.getBackground().getFills().get(0).getFill(), CornerRadii.EMPTY, Insets.EMPTY)); - }, pane.backgroundProperty())); - - ((Pane)pickerDecorator.lookup(".jfx-decorator-content-container")).borderProperty().bind(Bindings.createObjectBinding(()->{ - return new Border(new BorderStroke((Color) pane.getBackground().getFills().get(0).getFill(), BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(0, 4, 4, 4))); - }, pane.backgroundProperty())); - }); - }; - - - container.getChildren().add(tabs); - - this.getChildren().add(container); - this.setPadding(new Insets(0)); - - dialog.setScene(customScene); - dialog.addEventHandler(KeyEvent.ANY, keyEventListener); - } - - private void updateColorFromUserInput(String colorWebString) { - if(!systemChange){ - userChange = true; - try { - curvedColorPicker.setColor(Color.valueOf(colorWebString)); - } catch (Exception e) { - // if color is not valid then do nothing - } - userChange = false; - } - } - - private final EventHandler keyEventListener = key -> { - switch (key.getCode()) { - case ESCAPE : - close(); - break; - case ENTER: - close(); - this.customColorProperty.set(curvedColorPicker.getColor(curvedColorPicker.getSelectedIndex())); - this.onSave.run(); - break; - default: - break; - } - }; - - private void close(){ - dialog.setScene(null); - dialog.close(); - } - - public void setCurrentColor(Color currentColor) { - this.currentColorProperty.set(currentColor); - } - - Color getCurrentColor() { - return currentColorProperty.get(); - } - - ObjectProperty customColorProperty() { - return customColorProperty; - } - - void setCustomColor(Color color) { - customColorProperty.set(color); - } - - Color getCustomColor() { - return customColorProperty.get(); - } - - public Runnable getOnSave() { - return onSave; - } - - public void setOnSave(Runnable onSave) { - this.onSave = onSave; - } - - public void setOnHidden(EventHandler onHidden) { - dialog.setOnHidden(onHidden); - } - - public void show() { - dialog.setOpacity(0); - // pickerDecorator.setOpacity(0); - if (dialog.getOwner() != null) { - dialog.widthProperty().addListener(positionAdjuster); - dialog.heightProperty().addListener(positionAdjuster); - positionAdjuster.invalidated(null); - } - if (dialog.getScene() == null) dialog.setScene(customScene); - curvedColorPicker.preAnimate(); - dialog.show(); - if(initOnce) { - initRun.run(); - initOnce = false; - } - - Timeline timeline = new Timeline(new KeyFrame(Duration.millis(120), - // new KeyValue(pickerDecorator.opacityProperty(), 1, Interpolator.EASE_BOTH), - new KeyValue(dialog.opacityProperty(), 1, Interpolator.EASE_BOTH))); - timeline.setOnFinished((finish)-> curvedColorPicker.animate()); - timeline.play(); - // CachedTransition showStage = new CachedTransition( - // pickerDecorator,) - // {{ - // this.setDelay(Duration.millis(0)); - // this.setCycleDuration(Duration.millis(320)); - // }}; - - } - - - // add option to show color picker using JFX Dialog - private InvalidationListener positionAdjuster = new InvalidationListener() { - @Override - public void invalidated(Observable ignored) { - if (Double.isNaN(dialog.getWidth()) || Double.isNaN(dialog.getHeight())) return; - dialog.widthProperty().removeListener(positionAdjuster); - dialog.heightProperty().removeListener(positionAdjuster); - fixPosition(); - } - }; - - private void fixPosition() { - Window w = dialog.getOwner(); - Screen s = com.sun.javafx.util.Utils.getScreen(w); - Rectangle2D sb = s.getBounds(); - double xR = w.getX() + w.getWidth(); - double xL = w.getX() - dialog.getWidth(); - double x, y; - if (sb.getMaxX() >= xR + dialog.getWidth()) x = xR; - else if (sb.getMinX() <= xL) x = xL; - else x = Math.max(sb.getMinX(), sb.getMaxX() - dialog.getWidth()); - y = Math.max(sb.getMinY(), Math.min(sb.getMaxY() - dialog.getHeight(), w.getY())); - dialog.setX(x); - dialog.setY(y); - } - - @Override public void layoutChildren() { - super.layoutChildren(); - if (dialog.getMinWidth() > 0 && dialog.getMinHeight() > 0) return; - double minWidth = Math.max(0, computeMinWidth(getHeight()) + (dialog.getWidth() - customScene.getWidth())); - double minHeight = Math.max(0, computeMinHeight(getWidth()) + (dialog.getHeight() - customScene.getHeight())); - dialog.setMinWidth(minWidth); - dialog.setMinHeight(minHeight); - } - - -} +/* + * 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.skins; + +import com.jfoenix.controls.*; +import com.jfoenix.svg.SVGGlyph; +import com.jfoenix.transitions.JFXFillTransition; +import javafx.animation.*; +import javafx.application.Platform; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.binding.Bindings; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.event.EventHandler; +import javafx.geometry.Insets; +import javafx.geometry.Rectangle2D; +import javafx.scene.Scene; +import javafx.scene.control.Label; +import javafx.scene.control.Tab; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.*; +import javafx.scene.paint.Color; +import javafx.scene.shape.Line; +import javafx.stage.*; +import javafx.util.Duration; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author Shadi Shaheen + * + */ +public class JFXCustomColorPickerDialog extends StackPane { + + private final Stage dialog = new Stage(); + // used for concurrency control and preventing FX-thread over use + private final AtomicInteger concurrencyController = new AtomicInteger(-1); + + private ObjectProperty currentColorProperty = new SimpleObjectProperty<>(Color.WHITE); + private ObjectProperty customColorProperty = new SimpleObjectProperty<>(Color.TRANSPARENT); + private Runnable onSave; + + private Scene customScene; + private JFXCustomColorPicker curvedColorPicker; + private ParallelTransition paraTransition; + private JFXDecorator pickerDecorator; + private boolean systemChange = false; + private boolean userChange = false; + private boolean initOnce = true; + private Runnable initRun; + + public JFXCustomColorPickerDialog(Window owner) { + getStyleClass().add("custom-color-dialog"); + if (owner != null) dialog.initOwner(owner); + dialog.initModality(Modality.APPLICATION_MODAL); + dialog.initStyle(StageStyle.TRANSPARENT); + dialog.setResizable(false); + + // create JFX Decorator + pickerDecorator = new JFXDecorator(dialog,this, false,false,false); + pickerDecorator.setOnCloseButtonAction(()-> close()); + pickerDecorator.setPickOnBounds(false); + // JFXDepthManager.setDepth(pickerDecorator, 2); + // StackPane decoratorContainer = new StackPane(pickerDecorator); + // decoratorContainer.setPadding(new Insets(20)); + // decoratorContainer.setStyle("-fx-background-color:TRANSPARENT;"); + // decoratorContainer.setPickOnBounds(false); + customScene = new Scene(pickerDecorator, Color.TRANSPARENT); + final Scene ownerScene = owner.getScene(); + if (ownerScene != null) { + if (ownerScene.getUserAgentStylesheet() != null) + customScene.setUserAgentStylesheet(ownerScene.getUserAgentStylesheet()); + customScene.getStylesheets().addAll(ownerScene.getStylesheets()); + } + curvedColorPicker = new JFXCustomColorPicker(); + + StackPane pane = new StackPane(curvedColorPicker); + pane.setPadding(new Insets(18)); + + VBox container = new VBox(); + container.getChildren().add(pane); + + JFXTabPane tabs = new JFXTabPane(); + + JFXTextField rgbField = new JFXTextField(); + JFXTextField hsbField = new JFXTextField(); + JFXTextField hexField = new JFXTextField(); + + rgbField.setStyle("-fx-background-color:TRANSPARENT;-fx-font-weight: BOLD;-fx-prompt-text-fill: #808080; -fx-alignment: top-left ; -fx-max-width: 300;"); + rgbField.setPromptText("RGB Color"); + rgbField.textProperty().addListener((o,oldVal,newVal)-> updateColorFromUserInput(newVal)); + + hsbField.setStyle("-fx-background-color:TRANSPARENT;-fx-font-weight: BOLD;-fx-prompt-text-fill: #808080; -fx-alignment: top-left ; -fx-max-width: 300;"); + hsbField.setPromptText("HSB Color"); + hsbField.textProperty().addListener((o,oldVal,newVal)-> updateColorFromUserInput(newVal)); + + hexField.setStyle("-fx-background-color:TRANSPARENT;-fx-font-weight: BOLD;-fx-prompt-text-fill: #808080; -fx-alignment: top-left ; -fx-max-width: 300;"); + hexField.setPromptText("#HEX Color"); + hexField.textProperty().addListener((o,oldVal,newVal)-> updateColorFromUserInput(newVal)); + + StackPane tabContent = new StackPane(); + tabContent.getChildren().add(rgbField); + tabContent.setMinHeight(100); + + Tab rgbTab = new Tab("RGB"); + rgbTab.setContent(tabContent); + Tab hsbTab = new Tab("HSB"); + hsbTab.setContent(hsbField); + Tab hexTab = new Tab("HEX"); + hexTab.setContent(hexField); + + tabs.getTabs().add(rgbTab); + tabs.getTabs().add(hsbTab); + tabs.getTabs().add(hexTab); + + curvedColorPicker.selectedPath.addListener((o,oldVal,newVal)->{ + if(paraTransition!=null) paraTransition.stop(); + Region tabsHeader = (Region) tabs.lookup(".tab-header-background"); + pane.backgroundProperty().unbind(); + tabsHeader.backgroundProperty().unbind(); + JFXFillTransition fillTransition = new JFXFillTransition(Duration.millis(240), pane, (Color)oldVal.getFill(), (Color)newVal.getFill()); + JFXFillTransition tabsFillTransition = new JFXFillTransition(Duration.millis(240), tabsHeader, (Color)oldVal.getFill(), (Color)newVal.getFill()); + paraTransition = new ParallelTransition(fillTransition, tabsFillTransition); + paraTransition.setOnFinished((finish)->{ + tabsHeader.backgroundProperty().bind(Bindings.createObjectBinding(()->{ + return new Background(new BackgroundFill(newVal.getFill(), CornerRadii.EMPTY, Insets.EMPTY)); + }, newVal.fillProperty())); + pane.backgroundProperty().bind(Bindings.createObjectBinding(()->{ + return new Background(new BackgroundFill(newVal.getFill(), CornerRadii.EMPTY, Insets.EMPTY)); + }, newVal.fillProperty())); + }); + paraTransition.play(); + }); + + initRun = ()->{ + // change tabs labels font color according to the selected color + pane.backgroundProperty().addListener((o,oldVal,newVal)->{ + if (concurrencyController.getAndSet(1) == -1) { + Color fontColor = ((Color)newVal.getFills().get(0).getFill()).grayscale().getRed() > 0.5? Color.valueOf("rgba(40, 40, 40, 0.87)") : Color.valueOf("rgba(255, 255, 255, 0.87)"); + tabs.lookupAll(".tab").forEach(tabNode->tabNode.lookupAll(".tab-label").forEach(node-> ((Label)node).setTextFill(fontColor))); + tabs.lookupAll(".tab").forEach(tabNode->tabNode.lookupAll(".jfx-rippler").forEach(node-> ((JFXRippler)node).setRipplerFill(fontColor))); + ((Line)tabs.lookup(".tab-selected-line")).setStroke(fontColor); + pickerDecorator.lookupAll(".jfx-decorator-button").forEach(button->{ + ((JFXButton)button).setRipplerFill(fontColor); + ((SVGGlyph)((JFXButton)button).getGraphic()).setFill(fontColor); + }); + + Color newColor = (Color) newVal.getFills().get(0).getFill(); + String hex = String.format("#%02X%02X%02X", + (int)( newColor.getRed() * 255), + (int)( newColor.getGreen() * 255), + (int)( newColor.getBlue() * 255)); + String rgb = String.format("rgba(%d, %d, %d, 1)", + (int)( newColor.getRed() * 255), + (int)( newColor.getGreen() * 255), + (int)( newColor.getBlue() * 255)); + String hsb = String.format("hsl(%d, %d%%, %d%%)", + (int)( newColor.getHue()), + (int)(newColor.getSaturation()*100), + (int)(newColor.getBrightness()*100)); + + if(!userChange){ + systemChange = true; + rgbField.setText(rgb); + hsbField.setText(hsb); + hexField.setText(hex); + systemChange = false; + } + concurrencyController.getAndSet(-1); + } + }); + + // initial selected colors + Platform.runLater(()->{ + pane.setBackground(new Background(new BackgroundFill(curvedColorPicker.getColor(curvedColorPicker.getSelectedIndex()), CornerRadii.EMPTY, Insets.EMPTY))); + ((Region) tabs.lookup(".tab-header-background")).setBackground(new Background(new BackgroundFill(curvedColorPicker.getColor(curvedColorPicker.getSelectedIndex()), CornerRadii.EMPTY, Insets.EMPTY))); + Region tabsHeader = (Region) tabs.lookup(".tab-header-background"); + pane.backgroundProperty().unbind(); + tabsHeader.backgroundProperty().unbind(); + tabsHeader.backgroundProperty().bind(Bindings.createObjectBinding(()->{ + return new Background(new BackgroundFill(curvedColorPicker.selectedPath.get().getFill(), CornerRadii.EMPTY, Insets.EMPTY)); + }, curvedColorPicker.selectedPath.get().fillProperty())); + pane.backgroundProperty().bind(Bindings.createObjectBinding(()->{ + return new Background(new BackgroundFill(curvedColorPicker.selectedPath.get().getFill(), CornerRadii.EMPTY, Insets.EMPTY)); + }, curvedColorPicker.selectedPath.get().fillProperty())); + + // bind text field line color + rgbField.focusColorProperty().bind(Bindings.createObjectBinding(()->{ + return pane.getBackground().getFills().get(0).getFill(); + }, pane.backgroundProperty())); + hsbField.focusColorProperty().bind(Bindings.createObjectBinding(()->{ + return pane.getBackground().getFills().get(0).getFill(); + }, pane.backgroundProperty())); + hexField.focusColorProperty().bind(Bindings.createObjectBinding(()->{ + return pane.getBackground().getFills().get(0).getFill(); + }, pane.backgroundProperty())); + + + ((Pane)pickerDecorator.lookup(".jfx-decorator-buttons-container")).backgroundProperty().bind(Bindings.createObjectBinding(()->{ + return new Background(new BackgroundFill((Color) pane.getBackground().getFills().get(0).getFill(), CornerRadii.EMPTY, Insets.EMPTY)); + }, pane.backgroundProperty())); + + ((Pane)pickerDecorator.lookup(".jfx-decorator-content-container")).borderProperty().bind(Bindings.createObjectBinding(()->{ + return new Border(new BorderStroke((Color) pane.getBackground().getFills().get(0).getFill(), BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(0, 4, 4, 4))); + }, pane.backgroundProperty())); + }); + }; + + + container.getChildren().add(tabs); + + this.getChildren().add(container); + this.setPadding(new Insets(0)); + + dialog.setScene(customScene); + dialog.addEventHandler(KeyEvent.ANY, keyEventListener); + } + + private void updateColorFromUserInput(String colorWebString) { + if(!systemChange){ + userChange = true; + try { + curvedColorPicker.setColor(Color.valueOf(colorWebString)); + } catch (Exception e) { + // if color is not valid then do nothing + } + userChange = false; + } + } + + private final EventHandler keyEventListener = key -> { + switch (key.getCode()) { + case ESCAPE : + close(); + break; + case ENTER: + close(); + this.customColorProperty.set(curvedColorPicker.getColor(curvedColorPicker.getSelectedIndex())); + this.onSave.run(); + break; + default: + break; + } + }; + + private void close(){ + dialog.setScene(null); + dialog.close(); + } + + public void setCurrentColor(Color currentColor) { + this.currentColorProperty.set(currentColor); + } + + Color getCurrentColor() { + return currentColorProperty.get(); + } + + ObjectProperty customColorProperty() { + return customColorProperty; + } + + void setCustomColor(Color color) { + customColorProperty.set(color); + } + + Color getCustomColor() { + return customColorProperty.get(); + } + + public Runnable getOnSave() { + return onSave; + } + + public void setOnSave(Runnable onSave) { + this.onSave = onSave; + } + + public void setOnHidden(EventHandler onHidden) { + dialog.setOnHidden(onHidden); + } + + public void show() { + dialog.setOpacity(0); + // pickerDecorator.setOpacity(0); + if (dialog.getOwner() != null) { + dialog.widthProperty().addListener(positionAdjuster); + dialog.heightProperty().addListener(positionAdjuster); + positionAdjuster.invalidated(null); + } + if (dialog.getScene() == null) dialog.setScene(customScene); + curvedColorPicker.preAnimate(); + dialog.show(); + if(initOnce) { + initRun.run(); + initOnce = false; + } + + Timeline timeline = new Timeline(new KeyFrame(Duration.millis(120), + // new KeyValue(pickerDecorator.opacityProperty(), 1, Interpolator.EASE_BOTH), + new KeyValue(dialog.opacityProperty(), 1, Interpolator.EASE_BOTH))); + timeline.setOnFinished((finish)-> curvedColorPicker.animate()); + timeline.play(); + // CachedTransition showStage = new CachedTransition( + // pickerDecorator,) + // {{ + // this.setDelay(Duration.millis(0)); + // this.setCycleDuration(Duration.millis(320)); + // }}; + + } + + + // add option to show color picker using JFX Dialog + private InvalidationListener positionAdjuster = new InvalidationListener() { + @Override + public void invalidated(Observable ignored) { + if (Double.isNaN(dialog.getWidth()) || Double.isNaN(dialog.getHeight())) return; + dialog.widthProperty().removeListener(positionAdjuster); + dialog.heightProperty().removeListener(positionAdjuster); + fixPosition(); + } + }; + + private void fixPosition() { + Window w = dialog.getOwner(); + Screen s = com.sun.javafx.util.Utils.getScreen(w); + Rectangle2D sb = s.getBounds(); + double xR = w.getX() + w.getWidth(); + double xL = w.getX() - dialog.getWidth(); + double x, y; + if (sb.getMaxX() >= xR + dialog.getWidth()) x = xR; + else if (sb.getMinX() <= xL) x = xL; + else x = Math.max(sb.getMinX(), sb.getMaxX() - dialog.getWidth()); + y = Math.max(sb.getMinY(), Math.min(sb.getMaxY() - dialog.getHeight(), w.getY())); + dialog.setX(x); + dialog.setY(y); + } + + @Override public void layoutChildren() { + super.layoutChildren(); + if (dialog.getMinWidth() > 0 && dialog.getMinHeight() > 0) return; + double minWidth = Math.max(0, computeMinWidth(getHeight()) + (dialog.getWidth() - customScene.getWidth())); + double minHeight = Math.max(0, computeMinHeight(getWidth()) + (dialog.getHeight() - customScene.getHeight())); + dialog.setMinWidth(minWidth); + dialog.setMinHeight(minHeight); + } + + +} diff --git a/src/com/jfoenix/skins/JFXDatePickerContent.java b/jfoenix/src/main/java/com/jfoenix/skins/JFXDatePickerContent.java similarity index 97% rename from src/com/jfoenix/skins/JFXDatePickerContent.java rename to jfoenix/src/main/java/com/jfoenix/skins/JFXDatePickerContent.java index b2c8767b..3bdedac4 100644 --- a/src/com/jfoenix/skins/JFXDatePickerContent.java +++ b/jfoenix/src/main/java/com/jfoenix/skins/JFXDatePickerContent.java @@ -1,759 +1,759 @@ -/* - * 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.skins; - -import com.jfoenix.controls.JFXButton; -import com.jfoenix.controls.JFXDatePicker; -import com.jfoenix.controls.JFXListCell; -import com.jfoenix.controls.JFXListView; -import com.jfoenix.svg.SVGGlyph; -import com.jfoenix.transitions.CachedTransition; -import javafx.animation.Animation.Status; -import javafx.animation.*; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.geometry.Insets; -import javafx.geometry.Pos; -import javafx.scene.Node; -import javafx.scene.SnapshotParameters; -import javafx.scene.control.*; -import javafx.scene.image.ImageView; -import javafx.scene.image.WritableImage; -import javafx.scene.input.KeyEvent; -import javafx.scene.input.MouseButton; -import javafx.scene.input.MouseEvent; -import javafx.scene.layout.*; -import javafx.scene.paint.Color; -import javafx.scene.shape.Rectangle; -import javafx.scene.text.Font; -import javafx.scene.text.FontWeight; -import javafx.scene.text.TextAlignment; -import javafx.util.Callback; -import javafx.util.Duration; - -import java.time.DateTimeException; -import java.time.LocalDate; -import java.time.YearMonth; -import java.time.chrono.ChronoLocalDate; -import java.time.chrono.Chronology; -import java.time.format.DateTimeFormatter; -import java.time.format.DecimalStyle; -import java.time.temporal.ChronoUnit; -import java.time.temporal.WeekFields; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; - -import static java.time.temporal.ChronoUnit.*; - -/** - * @author Shadi Shaheen - * - */ -public class JFXDatePickerContent extends VBox { - - protected JFXDatePicker datePicker; - private JFXButton backMonthButton; - private JFXButton forwardMonthButton; - private ObjectProperty