Skip to content

Commit

Permalink
Added styled text fields (#894)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jugen committed Jan 8, 2020
1 parent fa2074b commit 63cda11
Show file tree
Hide file tree
Showing 5 changed files with 287 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.fxmisc.richtext;

import org.fxmisc.richtext.model.SimpleEditableStyledDocument;

import javafx.scene.text.TextFlow;

/**
* A TextField that uses inline CSS, i.e. <code>setStyle(String)</code>, to define the styles of text segments.
* <p>Use CSS Style Class ".styled-text-field" for styling the control.
* @author Jurgen
*/
public class InlineCssTextField extends StyledTextField<String,String>
{
public InlineCssTextField() {
super( "", TextFlow::setStyle, "", TextExt::setStyle, new SimpleEditableStyledDocument<>("", "") );
}

public InlineCssTextField( String text ) {
this(); replaceText( text );
getUndoManager().forgetHistory();
getUndoManager().mark();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package org.fxmisc.richtext;

import java.util.Collection;
import java.util.Collections;

import org.fxmisc.richtext.model.SimpleEditableStyledDocument;

/**
* A TextField that uses style classes, i.e. <code>getStyleClass().add(String)</code>, to define the styles of text segments.
* <p>Use CSS Style Class ".styled-text-field" for styling the control.
* @author Jurgen
*/
public class StyleClassedTextField extends StyledTextField<Collection<String>, Collection<String>>
{
public StyleClassedTextField() {
super(
Collections.<String>emptyList(),
(paragraph, styleClasses) -> paragraph.getStyleClass().addAll(styleClasses),
Collections.<String>emptyList(),
(text, styleClasses) -> text.getStyleClass().addAll(styleClasses),
new SimpleEditableStyledDocument<>( Collections.<String>emptyList(), Collections.<String>emptyList() )
);
}

public StyleClassedTextField( String text ) {
this(); replaceText( text );
getUndoManager().forgetHistory();
getUndoManager().mark();
}

/**
* Convenient method to append text together with a single style class.
*/
public void append( String text, String styleClass ) {
insert( getLength(), text, styleClass );
}

/**
* Convenient method to insert text together with a single style class.
*/
public void insert( int position, String text, String styleClass ) {
replace( position, position, text, Collections.singleton( styleClass ) );
}

/**
* Convenient method to replace text together with a single style class.
*/
public void replace( int start, int end, String text, String styleClass ) {
replace( start, end, text, Collections.singleton( styleClass ) );
}

/**
* Convenient method to assign a single style class.
*/
public void setStyleClass( int from, int to, String styleClass ) {
setStyle( from, to, Collections.singletonList( styleClass ) );
}
}
158 changes: 158 additions & 0 deletions richtextfx/src/main/java/org/fxmisc/richtext/StyledTextField.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package org.fxmisc.richtext;

import java.util.List;
import java.util.function.BiConsumer;
import java.util.regex.Pattern;

import org.fxmisc.richtext.model.EditableStyledDocument;

import javafx.application.Application;
import javafx.beans.NamedArg;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ObjectPropertyBase;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.AccessibleRole;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.text.TextFlow;

public class StyledTextField<PS, S> extends StyledTextArea
{
private final Pattern VERTICAL_WHITESPACE = Pattern.compile( "\\v" );
private final static String STYLE_SHEET;
private final static double HEIGHT;
static {
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();

// Ugly hack to get a TextFields default height :(
// as it differs between Caspian, Modena, etc.
TextField tf = new TextField( "GetHeight" );
new Scene(tf); tf.applyCss(); tf.layout();
HEIGHT = tf.getHeight();
}

private boolean selectAll = true;


public StyledTextField(@NamedArg("initialParagraphStyle") PS initialParagraphStyle,
@NamedArg("applyParagraphStyle") BiConsumer<TextFlow, PS> applyParagraphStyle,
@NamedArg("initialTextStyle") S initialTextStyle,
@NamedArg("applyStyle") BiConsumer<? super TextExt, S> applyStyle,
@NamedArg("document") EditableStyledDocument<PS, String, S> document)
{
super( initialParagraphStyle, applyParagraphStyle, initialTextStyle, applyStyle, document, true );

getStylesheets().add( STYLE_SHEET );
getStyleClass().setAll( "styled-text-field" );

setAccessibleRole( AccessibleRole.TEXT_FIELD );
setPrefSize( 135, HEIGHT );

addEventFilter( KeyEvent.KEY_PRESSED, KE -> {
if ( KE.getCode() == KeyCode.ENTER ) {
fireEvent( new ActionEvent( this, null ) );
KE.consume();
}
else if ( KE.getCode() == KeyCode.TAB ) {
traverse( this.getParent(), this, KE.isShiftDown() ? -1 : +1 );
KE.consume();
}
});

addEventFilter( MouseEvent.MOUSE_PRESSED, ME -> selectAll = isFocused() );

focusedProperty().addListener( (ob,was,focused) -> {
if ( ! was && focused && selectAll ) {
selectRange( getLength(), 0 );
}
else if ( ! focused && was ) {
moveTo( 0 ); requestFollowCaret();
}
selectAll = true;
});
}

/*
* There's no public API to move the focus forward or backward
* without explicitly knowing the node. So here's a basic local
* implementation to accomplish that.
*/
private Node traverse( Parent p, Node from, int dir )
{
if ( p == null ) return null;

List<Node> nodeList = p.getChildrenUnmodifiable();
int len = nodeList.size();
int neighbor = -1;

if ( from != null ) while ( ++neighbor < len && nodeList.get(neighbor) != from );
else if ( dir == 1 ) neighbor = -1;
else neighbor = len;

for ( neighbor += dir; neighbor > -1 && neighbor < len; neighbor += dir ) {

Node target = nodeList.get( neighbor );

if ( target instanceof Pane || target instanceof Group ) {
target = traverse( (Parent) target, null, dir ); // down
if ( target != null ) return target;
}
else if ( target.isVisible() && ! target.isDisabled() && target.isFocusTraversable() ) {
target.requestFocus();
return target;
}
}

return traverse( p.getParent(), p, dir ); // up
}


public void setText( String text )
{
replaceText( text );
}

/**
* The action handler associated with this text field, or
* {@code null} if no action handler is assigned.
*
* The action handler is normally called when the user types the ENTER key.
*/
private ObjectProperty<EventHandler<ActionEvent>> onAction = new ObjectPropertyBase<EventHandler<ActionEvent>>() {
@Override
protected void invalidated() {
setEventHandler(ActionEvent.ACTION, get());
}

@Override
public Object getBean() {
return StyledTextField.this;
}

@Override
public String getName() {
return "onAction";
}
};
public final ObjectProperty<EventHandler<ActionEvent>> onActionProperty() { return onAction; }
public final EventHandler<ActionEvent> getOnAction() { return onActionProperty().get(); }
public final void setOnAction(EventHandler<ActionEvent> value) { onActionProperty().set(value); }

@Override
public void replaceText( int start, int end, String text )
{
super.replaceText( start, end, VERTICAL_WHITESPACE.matcher( text ).replaceAll( " " ) );
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
.styled-text-field
{
-fx-cursor: text;
-fx-text-fill: -fx-text-inner-color;
-fx-background-color: -fx-shadow-highlight-color, -fx-text-box-border, -fx-control-inner-background;
-fx-background-insets: 0, 1, 2;
-fx-background-radius: 3, 2, 2;
-fx-padding: 3 5 4 5;
}
.styled-text-field:focused
{
-fx-background-color: -fx-focus-color, -fx-text-box-border, -fx-control-inner-background;
-fx-background-insets: -0.4, 1, 2;
-fx-background-radius: 3.4, 2, 2;
}
.styled-text-field:disabled
{
-fx-opacity: -fx-disabled-opacity;
}
.styled-text-field .main-selection
{
-fx-fill: #0093ff;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
.styled-text-field
{
-fx-cursor: text;
-fx-text-fill: -fx-text-inner-color;
-fx-background-color: linear-gradient(to bottom, derive(-fx-text-box-border, -10%), -fx-text-box-border),
linear-gradient(from 0px 0px to 0px 5px, derive(-fx-control-inner-background, -9%), -fx-control-inner-background);
-fx-background-insets: 0, 1;
-fx-background-radius: 3, 2;
-fx-padding: 4 7 4 7;
}
.styled-text-field:focused
{
-fx-background-color: -fx-focus-color, -fx-control-inner-background, -fx-faint-focus-color,
linear-gradient(from 0px 0px to 0px 5px, derive(-fx-control-inner-background, -9%), -fx-control-inner-background);
-fx-background-insets: -0.2, 1, -1.4, 3;
-fx-background-radius: 3, 2, 4, 0;
}
.styled-text-field:disabled
{
-fx-opacity: 0.4;
}
.styled-text-field .main-selection
{
-fx-fill: #0096C9;
}

0 comments on commit 63cda11

Please sign in to comment.