package floatyfield;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.animation.FadeTransition;
import javafx.animation.TranslateTransition;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.util.Duration;
public class FloatyFieldView implements Initializable {
private static final String ACTIVE_FLOATY_FIELD = "floaty-field-active";
private static final String INACTIVE_FLOATY_FIELD = "floaty-field-inactive";
@FXML
private Label label;
@FXML
private TextField field;
/**
* Constructor for manual instantiation.
*/
public FloatyFieldView(Label label, TextField field) {
this.label = label;
this.field = field;
initialize(null, null);
}
/**
* Used for FXML instantiation only.
*/
public FloatyFieldView() {
}
@Override
public void initialize(URL url, ResourceBundle r) {
BooleanBinding textNotEmpty = field.textProperty().isEqualTo("").not();
BooleanBinding labelActive = textNotEmpty.or(field.focusedProperty());
BooleanBinding fieldActive = textNotEmpty.and(field.focusedProperty());
labelActive.addListener(new LabelFadeIn());
fieldActive.addListener(new FieldActiveStyler());
label.setVisible(false);
label.visibleProperty().bind(labelActive);
label.textProperty().bind(field.promptTextProperty());
label.setLabelFor(field);
// make sure that any click within this control will give focus to the input field
field.getParent().setOnMouseClicked((e) -> { field.requestFocus(); });
}
public final StringProperty promptTextProperty() {
return field.promptTextProperty();
}
public final StringProperty textProperty() {
return field.textProperty();
}
public final BooleanProperty disableProperty() {
return field.disableProperty();
}
public Label getLabel() {
return label;
}
public void setLabel(Label label) {
this.label = label;
}
public TextField getField() {
return field;
}
public void setField(TextField field) {
this.field = field;
}
public String getText() {
return field.getText();
}
private final class LabelFadeIn implements ChangeListener<Boolean> {
@Override
public void changed(ObservableValue<? extends Boolean> o, Boolean ob, Boolean nb) {
if (nb) {
FadeTransition ft;
TranslateTransition tt;
ft = new FadeTransition(Duration.millis(350), label);
ft.setFromValue(0d);
ft.setToValue(1d);
ft.play();
double y = label.localToParent(0d, 0d).getY();
tt = new TranslateTransition(Duration.millis(500), label);
tt.setFromY(y + 5);
tt.setToY(y);
tt.setCycleCount(1);
tt.setAutoReverse(true);
// if this doesn't happen then the translate property quickly becomes out of sync
// when rapidly tabbing
tt.setOnFinished((e) -> { label.setTranslateY(0d); });
tt.play();
}
}
}
private final class FieldActiveStyler implements ChangeListener<Boolean> {
@Override
public void changed(ObservableValue<? extends Boolean> o, Boolean ob, Boolean nb) {
if (nb) {
label.getStyleClass().remove(INACTIVE_FLOATY_FIELD);
label.getStyleClass().add(ACTIVE_FLOATY_FIELD);
} else {
label.getStyleClass().remove(ACTIVE_FLOATY_FIELD);
label.getStyleClass().add(INACTIVE_FLOATY_FIELD);
}
}
}
}