From a804a040f730dbc259d6e74f36964981cb389512 Mon Sep 17 00:00:00 2001 From: Jurgen Date: Tue, 14 Jan 2020 16:23:43 +0200 Subject: [PATCH 1/3] Added place holder enhancement --- .../fxmisc/richtext/GenericStyledArea.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/GenericStyledArea.java b/richtextfx/src/main/java/org/fxmisc/richtext/GenericStyledArea.java index 9491f6903..6ffcd40d1 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/GenericStyledArea.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/GenericStyledArea.java @@ -18,6 +18,7 @@ import javafx.application.Platform; import javafx.beans.NamedArg; +import javafx.beans.binding.Bindings; import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; @@ -371,6 +372,14 @@ public Node getParagraphGraphic( int parNdx ) { return getCell(parNdx).getGraphic(); } + /** + * This Node is shown to the user, centered over the area, when the area has no text content. + */ + private ObjectProperty placeHolderProp = new SimpleObjectProperty<>(this, "placeHolder", null); + public final ObjectProperty placeholderProperty() { return placeHolderProp; } + public final void setPlaceholder(Node value) { placeHolderProp.set(value); } + public final Node getPlaceholder() { return placeHolderProp.get(); } + private ObjectProperty contextMenu = new SimpleObjectProperty<>(null); @Override public final ObjectProperty contextMenuObjectProperty() { return contextMenu; } // Don't remove as FXMLLoader doesn't recognise default methods ! @@ -787,6 +796,41 @@ public GenericStyledArea( .subscribe(evt -> Event.fireEvent(this, evt)); new GenericStyledAreaBehavior(this); + + // Setup place holder visibility & placement + final Val showPlaceholder = Val.create + ( + () -> getLength() == 0 && ! isFocused(), + lengthProperty(), focusedProperty() + ); + + placeHolderProp.addListener( (ob,oldNode,newNode) -> { + if ( oldNode != null ) { + oldNode.visibleProperty().unbind(); + oldNode.layoutXProperty().unbind(); + oldNode.layoutYProperty().unbind(); + getChildren().remove( oldNode ); + setClip( null ); + } + if ( newNode != null ) { + newNode.visibleProperty().bind( showPlaceholder ); + configurePlaceholder( newNode ); + getChildren().add( newNode ); + } + }); + } + + protected void configurePlaceholder( Node placeholder ) + { + placeholder.layoutYProperty().bind( Bindings.createDoubleBinding( () -> + (getHeight() - placeholder.getLayoutBounds().getHeight()) / 2, + heightProperty(), placeholder.layoutBoundsProperty() ) + ); + + placeholder.layoutXProperty().bind( Bindings.createDoubleBinding( () -> + (getWidth() - placeholder.getLayoutBounds().getWidth()) / 2, + widthProperty(), placeholder.layoutBoundsProperty() ) + ); } /* ********************************************************************** * From 757f83eb8435bdb42a2c3e61e726f622522c5e9f Mon Sep 17 00:00:00 2001 From: Jurgen Date: Tue, 14 Jan 2020 16:45:27 +0200 Subject: [PATCH 2/3] Added prompt text to StyledTextField --- .../org/fxmisc/richtext/StyledTextField.java | 73 ++++++++++++++++++- 1 file changed, 70 insertions(+), 3 deletions(-) diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/StyledTextField.java b/richtextfx/src/main/java/org/fxmisc/richtext/StyledTextField.java index 915eac2b7..88fe096d6 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/StyledTextField.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/StyledTextField.java @@ -10,6 +10,7 @@ import javafx.application.Application; import javafx.beans.NamedArg; +import javafx.beans.binding.Bindings; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectPropertyBase; import javafx.css.CssMetaData; @@ -28,6 +29,10 @@ import javafx.scene.input.KeyEvent; import javafx.scene.input.MouseEvent; import javafx.scene.layout.Pane; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; +import javafx.scene.shape.Rectangle; +import javafx.scene.text.Text; import javafx.scene.text.TextAlignment; import javafx.scene.text.TextFlow; @@ -51,19 +56,26 @@ public abstract class StyledTextField extends StyledTextArea "-fx-alignment", (StyleConverter) StyleConverter.getEnumConverter(TextAlignment.class), TextAlignment.LEFT, s -> (StyleableObjectProperty) s.alignmentProperty() ); + + private final static CssMetaData PROMPT_TEXT_FILL = new CustomCssMetaData<>( + "-fx-prompt-text-fill", (StyleConverter) StyleConverter.getPaintConverter(), + Color.GRAY, s -> (StyleableObjectProperty) s.promptTextFillProperty() + ); + private final static Pattern VERTICAL_WHITESPACE = Pattern.compile( "\\v+" ); private final static String STYLE_SHEET; private final static double HEIGHT; static { + List> styleables = new ArrayList<>(GenericStyledArea.getClassCssMetaData()); + styleables.add( PROMPT_TEXT_FILL ); styleables.add( TEXT_ALIGNMENT ); + CSS_META_DATA_LIST = Collections.unmodifiableList(styleables); + String globalCSS = System.getProperty( "javafx.userAgentStylesheetUrl" ); // JavaFX preference! if ( globalCSS == null ) globalCSS = Application.getUserAgentStylesheet(); if ( globalCSS == null ) globalCSS = Application.STYLESHEET_MODENA; globalCSS = "styled-text-field-"+ globalCSS.toLowerCase() +".css"; STYLE_SHEET = StyledTextField.class.getResource( globalCSS ).toExternalForm(); - List> styleables = new ArrayList<>(GenericStyledArea.getClassCssMetaData()); - styleables.add(TEXT_ALIGNMENT); CSS_META_DATA_LIST = Collections.unmodifiableList(styleables); - // Ugly hack to get a TextFields default height :( // as it differs between Caspian, Modena, etc. TextField tf = new TextField( "GetHeight" ); @@ -73,6 +85,7 @@ public abstract class StyledTextField extends StyledTextArea private boolean selectAll = true; private StyleableObjectProperty textAlignment; + private StyleableObjectProperty promptFillProp; public StyledTextField(@NamedArg("initialParagraphStyle") PS initialParagraphStyle, @@ -194,6 +207,60 @@ public String getName() { public final EventHandler getOnAction() { return onActionProperty().get(); } public final void setOnAction(EventHandler value) { onActionProperty().set(value); } + + /** + * The prompt text to display or null if no prompt text is to be displayed. + *

The Text will be aligned according to the text fields alignment setting and have a default + * text fill of GRAY unless you have changed it by any means, e.g. with CSS "-fx-prompt-text-fill" + */ + public final ObjectProperty promptTextProperty() { return placeholderProperty(); } + public final Text getPromptText() { return getPlaceholder() instanceof Text ? (Text) getPlaceholder() : null; } + public final void setPromptText( Text value ) { setPlaceholder( value ); } + @Override protected void configurePlaceholder( Node placeholder ) + { + placeholder.layoutYProperty().bind( Bindings.createDoubleBinding( () -> + (getHeight() - placeholder.getLayoutBounds().getHeight()) / 2 + Math.abs( placeholder.getLayoutBounds().getMinY() ), + heightProperty(), placeholder.layoutBoundsProperty() ) + ); + + placeholder.layoutXProperty().bind( Bindings.createDoubleBinding( () -> calcHorizontalPos(), + widthProperty(), placeholder.layoutBoundsProperty(), paddingProperty(), alignmentProperty() ) + ); + + if ( placeholder instanceof Text && ((Text) placeholder).getFill() == Color.BLACK ) { + ((Text) placeholder).fillProperty().bind( promptTextFillProperty() ); + } + } + + private final ObjectProperty promptTextFillProperty() { + if ( promptFillProp == null ) { + promptFillProp = new CustomStyleableProperty<>( Color.GRAY, "promptFill", this, PROMPT_TEXT_FILL ); + } + return promptFillProp; + } + + private double calcHorizontalPos() + { + double leftPad = getPadding().getLeft(); + double rightPad = getPadding().getRight(); + double promptWidth = getPlaceholder().getLayoutBounds().getWidth(); + TextAlignment alignment = getAlignment(); + double alignmentPadding = leftPad; + + if ( alignment == TextAlignment.RIGHT ) alignmentPadding = rightPad; + else if ( alignment == TextAlignment.CENTER ) alignmentPadding = 0; + + if ( promptWidth < (getWidth() - alignmentPadding) ) setClip( null ); + else setClip( new Rectangle( getWidth(), getHeight() ) ); + + switch ( alignment ) + { + case CENTER : return (getWidth() - promptWidth) / 2; + case RIGHT : return getWidth() - rightPad - promptWidth; + default : return leftPad; + } + } + @Override public void replaceText( int start, int end, String text ) { From 6a36fc1db864e80b079835e1d72b0f718c8cd6e7 Mon Sep 17 00:00:00 2001 From: Jurgen Date: Wed, 15 Jan 2020 08:51:40 +0200 Subject: [PATCH 3/3] Fixed place holder layout issue --- .../main/java/org/fxmisc/richtext/GenericStyledArea.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/GenericStyledArea.java b/richtextfx/src/main/java/org/fxmisc/richtext/GenericStyledArea.java index 6ffcd40d1..55a3ec05e 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/GenericStyledArea.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/GenericStyledArea.java @@ -16,7 +16,6 @@ import java.util.function.IntSupplier; import java.util.function.IntUnaryOperator; -import javafx.application.Platform; import javafx.beans.NamedArg; import javafx.beans.binding.Bindings; import javafx.beans.property.BooleanProperty; @@ -1442,6 +1441,11 @@ protected void layoutChildren() { followCaretRequested = false; paging = false; }); + + Node node = getPlaceholder(); + if (node != null && node.isResizable() && node.isManaged()) { + node.autosize(); + } } /* ********************************************************************** *