changeset 1057:425d61129c94

RT-21436: Scaling UI Controls down to VGA: TextArea -- Touch handles for caret and selection
author leifs
date Wed, 16 May 2012 14:22:07 -0700
parents 19dcd1df2147
children df882c23a791
files javafx-ui-controls/src/com/sun/javafx/scene/control/behavior/TextAreaBehavior.java javafx-ui-controls/src/com/sun/javafx/scene/control/skin/TextAreaSkin.java javafx-ui-controls/src/com/sun/javafx/scene/control/skin/TextInputControlSkin.java javafx-ui-controls/src/com/sun/javafx/scene/control/skin/caspian/embedded.css
diffstat 4 files changed, 224 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- a/javafx-ui-controls/src/com/sun/javafx/scene/control/behavior/TextAreaBehavior.java	Wed May 16 13:13:47 2012 -0400
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/behavior/TextAreaBehavior.java	Wed May 16 14:22:07 2012 -0700
@@ -258,8 +258,9 @@
                 final int anchor = textArea.getAnchor();
                 final int caretPosition = textArea.getCaretPosition();
                 if (e.getClickCount() < 2 &&
-                        anchor != caretPosition &&
-                        ((i > anchor && i < caretPosition) || (i < anchor && i > caretPosition))) {
+                    (PlatformUtil.isEmbedded() ||
+                     (anchor != caretPosition &&
+                      ((i > anchor && i < caretPosition) || (i < anchor && i > caretPosition))))) {
                     // if there is a selection, then we will NOT handle the
                     // press now, but will defer until the release. If you
                     // select some text and then press down, we change the
--- a/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/TextAreaSkin.java	Wed May 16 13:13:47 2012 -0400
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/TextAreaSkin.java	Wed May 16 14:22:07 2012 -0700
@@ -56,6 +56,7 @@
 import javafx.scene.control.TextArea;
 import javafx.scene.input.MouseEvent;
 import javafx.scene.layout.Region;
+import javafx.scene.layout.StackPane;
 import javafx.scene.paint.Paint;
 import javafx.scene.shape.MoveTo;
 import javafx.scene.shape.Path;
@@ -65,6 +66,7 @@
 import javafx.util.Duration;
 import java.util.List;
 
+import com.sun.javafx.PlatformUtil;
 import com.sun.javafx.scene.control.behavior.TextAreaBehavior;
 import com.sun.javafx.scene.text.HitInfo;
 
@@ -236,12 +238,62 @@
             IndexRange selection = textArea.getSelection();
             Bounds oldCaretBounds = caretPath.getBoundsInParent();
 
-            caretPath.getElements().clear();
             selectionHighlightGroup.getChildren().clear();
 
+            int caretPos = textArea.getCaretPosition();
+            int anchorPos = textArea.getAnchor();
+
+            if (PlatformUtil.isEmbedded()) {
+                // Install and resize the handles for caret and anchor.
+                if (selection.getLength() > 0) {
+                    contentView.getChildren().remove(caretHandle);
+                    if (!contentView.getChildren().contains(selectionHandle2)) {
+                        contentView.getChildren().addAll(selectionHandle1, selectionHandle2);
+                        selectionHandle1.resize(selectionHandle1.prefWidth(-1),
+                                                selectionHandle1.prefHeight(-1));
+                        selectionHandle2.resize(selectionHandle2.prefWidth(-1),
+                                                selectionHandle2.prefHeight(-1));
+                    }
+                } else {
+                    contentView.getChildren().remove(selectionHandle1);
+                    contentView.getChildren().remove(selectionHandle2);
+                    if (!contentView.getChildren().contains(caretHandle)) {
+                        contentView.getChildren().add(caretHandle);
+                        caretHandle.resize(caretHandle.prefWidth(-1),
+                                                caretHandle.prefHeight(-1));
+                    }
+                }
+
+                // Position the handle for the anchor. This could be handle1 or handle2.
+                // Do this before positioning the actual caret.
+                if (selection.getLength() > 0) {
+                    int paragraphIndex = paragraphNodes.getChildren().size();
+                    int paragraphOffset = textArea.getLength() + 1;
+                    Text paragraphNode = null;
+                    do {
+                        paragraphNode = (Text)paragraphNodes.getChildren().get(--paragraphIndex);
+                        paragraphOffset -= paragraphNode.getText().length() + 1;
+                    } while (anchorPos < paragraphOffset);
+
+                    paragraphNode.setImpl_caretPosition(anchorPos - paragraphOffset);
+                    caretPath.getElements().clear();
+                    caretPath.getElements().addAll(paragraphNode.getImpl_caretShape());
+                    caretPath.setLayoutX(paragraphNode.getLayoutX());
+                    caretPath.setLayoutY(paragraphNode.getLayoutY());
+
+                    Bounds b = caretPath.getBoundsInParent();
+                    if (caretPos < anchorPos) {
+                        selectionHandle2.setLayoutX(b.getMinX() - selectionHandle2.getWidth() / 2);
+                        selectionHandle2.setLayoutY(b.getMaxY());
+                    } else {
+                        selectionHandle1.setLayoutX(b.getMinX() - selectionHandle1.getWidth() / 2);
+                        selectionHandle1.setLayoutY(b.getMinY() - selectionHandle1.getHeight());
+                    }
+                }
+            }
+
             {
-                int caretPos = textArea.getCaretPosition();
-
+                // Position caret
                 int paragraphIndex = paragraphNodes.getChildren().size();
                 int paragraphOffset = textArea.getLength() + 1;
 
@@ -253,6 +305,7 @@
 
                 paragraphNode.setImpl_caretPosition(caretPos - paragraphOffset);
 
+                caretPath.getElements().clear();
                 caretPath.getElements().addAll(paragraphNode.getImpl_caretShape());
 
                 caretPath.setLayoutX(paragraphNode.getLayoutX());
@@ -293,6 +346,23 @@
                 end   = Math.max(0, end   - paragraphLength);
             }
 
+            if (PlatformUtil.isEmbedded()) {
+                // Position handle for the caret. This could be handle1 or handle2 when
+                // a selection is active.
+                Bounds b = caretPath.getBoundsInParent();
+                if (selection.getLength() > 0) {
+                    if (caretPos < anchorPos) {
+                        selectionHandle1.setLayoutX(b.getMinX() - selectionHandle1.getWidth() / 2);
+                        selectionHandle1.setLayoutY(b.getMinY() - selectionHandle1.getHeight());
+                    } else {
+                        selectionHandle2.setLayoutX(b.getMinX() - selectionHandle2.getWidth() / 2);
+                        selectionHandle2.setLayoutY(b.getMaxY());
+                    }
+                } else {
+                    caretHandle.setLayoutX(b.getMinX() - caretHandle.getWidth() / 2 + 1);
+                    caretHandle.setLayoutY(b.getMaxY());
+                }
+            }
 
             if (scrollPane.getPrefViewportWidth() == 0
                 || scrollPane.getPrefViewportHeight() == 0) {
@@ -566,8 +636,105 @@
         updatePrefViewportWidth();
         updatePrefViewportHeight();
         if (textArea.isFocused()) setCaretAnimating(true);
+
+        if (PlatformUtil.isEmbedded()) {
+            EventHandler<MouseEvent> handlePressHandler = new EventHandler<MouseEvent>() {
+                @Override public void handle(MouseEvent e) {
+                    pressX = e.getX();
+                    pressY = e.getY();
+                    e.consume();
+                }
+            };
+
+            caretHandle.setOnMousePressed(handlePressHandler);
+            selectionHandle1.setOnMousePressed(handlePressHandler);
+            selectionHandle2.setOnMousePressed(handlePressHandler);
+
+            caretHandle.setOnMouseDragged(new EventHandler<MouseEvent>() {
+                @Override public void handle(MouseEvent e) {
+                    Text textNode = getTextNode();
+                    Point2D p = new Point2D(caretHandle.getLayoutX() + e.getX() + pressX - textNode.getLayoutX(),
+                                            caretHandle.getLayoutY() + e.getY() - pressY - 6 - getTextTranslateY());
+                    HitInfo hit = textNode.impl_hitTestChar(translateCaretPosition(p));
+                    int pos = hit.getCharIndex();
+                    if (pos > 0) {
+                        int oldPos = textNode.getImpl_caretPosition();
+                        textNode.setImpl_caretPosition(pos);
+                        PathElement element = textNode.getImpl_caretShape()[0];
+                        if (element instanceof MoveTo && ((MoveTo)element).getY() > e.getY() - getTextTranslateY()) {
+                            hit.setCharIndex(pos - 1);
+                        }
+                        textNode.setImpl_caretPosition(oldPos);
+                    }
+                    positionCaret(hit, false, false);
+                    e.consume();
+                }
+            });
+
+            selectionHandle1.setOnMouseDragged(new EventHandler<MouseEvent>() {
+                @Override public void handle(MouseEvent e) {
+                    TextArea textArea = getSkinnable();
+                    Text textNode = getTextNode();
+                    Point2D tp = textNode.localToScene(0, 0);
+                    Point2D p = new Point2D(e.getSceneX() - tp.getX() + 10/*??*/ - pressX + selectionHandle1.getWidth() / 2,
+                                            e.getSceneY() - tp.getY() - pressY + selectionHandle1.getHeight() + 5);
+                    HitInfo hit = textNode.impl_hitTestChar(translateCaretPosition(p));
+                    int pos = hit.getCharIndex();
+                    if (textArea.getAnchor() < textArea.getCaretPosition()) {
+                        // Swap caret and anchor
+                        textArea.selectRange(textArea.getCaretPosition(), textArea.getAnchor());
+                    }
+                    if (pos > 0) {
+                        if (pos >= textArea.getAnchor()) {
+                            pos = textArea.getAnchor();
+                        }
+                        int oldPos = textNode.getImpl_caretPosition();
+                        textNode.setImpl_caretPosition(pos);
+                        PathElement element = textNode.getImpl_caretShape()[0];
+                        if (element instanceof MoveTo && ((MoveTo)element).getY() > e.getY() - getTextTranslateY()) {
+                            hit.setCharIndex(pos - 1);
+                        }
+                        textNode.setImpl_caretPosition(oldPos);
+                        positionCaret(hit, true, false);
+                    }
+                    e.consume();
+                }
+            });
+
+            selectionHandle2.setOnMouseDragged(new EventHandler<MouseEvent>() {
+                @Override public void handle(MouseEvent e) {
+                    TextArea textArea = getSkinnable();
+                    Text textNode = getTextNode();
+                    Point2D tp = textNode.localToScene(0, 0);
+                    Point2D p = new Point2D(e.getSceneX() - tp.getX() + 10/*??*/ - pressX + selectionHandle1.getWidth() / 2,
+                                            e.getSceneY() - tp.getY() - pressY - 6);
+                    HitInfo hit = textNode.impl_hitTestChar(translateCaretPosition(p));
+                    int pos = hit.getCharIndex();
+                    if (textArea.getAnchor() > textArea.getCaretPosition()) {
+                        // Swap caret and anchor
+                        textArea.selectRange(textArea.getCaretPosition(), textArea.getAnchor());
+                    }
+                    if (pos > 0) {
+                        if (pos <= textArea.getAnchor() + 1) {
+                            pos = Math.min(textArea.getAnchor() + 2, textArea.getLength());
+                        }
+                        int oldPos = textNode.getImpl_caretPosition();
+                        textNode.setImpl_caretPosition(pos);
+                        PathElement element = textNode.getImpl_caretShape()[0];
+                        if (element instanceof MoveTo && ((MoveTo)element).getY() > e.getY() - getTextTranslateY()) {
+                            hit.setCharIndex(pos - 1);
+                        }
+                        textNode.setImpl_caretPosition(oldPos);
+                        positionCaret(hit, true, false);
+                    }
+                    e.consume();
+                }
+            });
+        }
     }
 
+double pressX, pressY, pressSX, pressSY;
+
     private void createPromptNode() {
         if (promptNode == null && usePromptText.get()) {
             promptNode = new Text();
--- a/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/TextInputControlSkin.java	Wed May 16 13:13:47 2012 -0400
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/TextInputControlSkin.java	Wed May 16 14:22:07 2012 -0700
@@ -56,6 +56,7 @@
 import javafx.scene.input.InputMethodRequests;
 import javafx.scene.input.InputMethodTextRun;
 import javafx.scene.input.TouchEvent;
+import javafx.scene.layout.StackPane;
 import javafx.scene.paint.Color;
 import javafx.scene.paint.Paint;
 import javafx.scene.shape.ClosePath;
@@ -230,6 +231,10 @@
      */
     protected final Path caretPath = new Path();
 
+    protected StackPane caretHandle = null;
+    protected StackPane selectionHandle1 = null;
+    protected StackPane selectionHandle2 = null;
+
     private static boolean useFXVK = PlatformUtil.isEmbedded();
 
     /* For testing only */
@@ -287,6 +292,26 @@
         };
 
         if (PlatformUtil.isEmbedded()) {
+            caretHandle      = new StackPane();
+            selectionHandle1 = new StackPane();
+            selectionHandle1.setRotate(180);
+            selectionHandle2 = new StackPane();
+
+            caretHandle.visibleProperty().bind(new BooleanBinding() {
+                { bind(textInput.focusedProperty(), textInput.anchorProperty(), textInput.caretPositionProperty(),
+                       textInput.disabledProperty(), textInput.editableProperty(), displayCaret);}
+                @Override protected boolean computeValue() {
+                return displayCaret.get() && textInput.isFocused() &&
+                        textInput.getCaretPosition() == textInput.getAnchor() &&
+                        !textInput.isDisabled() && textInput.isEditable();
+                }
+            });
+
+
+            caretHandle.getStyleClass().setAll("caret-handle");
+            selectionHandle1.getStyleClass().setAll("selection-handle");
+            selectionHandle2.getStyleClass().add("selection-handle");
+
             textInput.focusedProperty().addListener(new InvalidationListener() {
                 @Override public void invalidated(Observable observable) {
                     if (useFXVK) {
--- a/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/caspian/embedded.css	Wed May 16 13:13:47 2012 -0400
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/caspian/embedded.css	Wed May 16 14:22:07 2012 -0700
@@ -67,6 +67,32 @@
 }
 
 
+.caret-handle {
+    -fx-background-color: black /*#ACACAC*/,
+                          linear-gradient(to bottom, #AFAFAF 0%, #DFDFDF 100%);
+    -fx-background-insets: 0, 1;
+    -fx-shape: "M11.974,2.579L20,12.358V28H4V12.356L11.974,2.579z";
+/*
+    -fx-shape: "M11.972,1L3,12v17h18V12L11.972,1L11.972,1z";
+    -fx-padding: 0.375em 0.291em 0.375em 0.291em;
+*/
+    -fx-padding: 0.45em 0.3em 0.45em 0.3em;
+    -fx-cursor: hand;
+}
+
+.selection-handle {
+    -fx-background-color: #0071bc /*-fx-accent*/,
+                          linear-gradient(to bottom, #0063AA 0%, #008AED 100%);
+    -fx-background-insets: 0, 1;
+    -fx-shape: "M10.974,2.579L19,12.358V28H3V12.356L10.974,2.579z";
+/*
+    -fx-shape: "M10.972,1L2,12v17h18V12L10.972,1L10.972,1z";
+    -fx-padding: 0.375em 0.291em 0.375em 0.291em;
+*/
+    -fx-padding: 0.45em 0.3em 0.45em 0.3em;
+    -fx-cursor: hand;
+}
+
 
 /*******************************************************************************
  *                                                                             *