Advertisement
Guest User

Untitled

a guest
Aug 16th, 2024
119
0
49 days
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 9.03 KB | Source Code | 0 0
  1. package playground;
  2.  
  3. import atlantafx.base.theme.Styles;
  4. import java.util.ArrayList;
  5. import java.util.Collections;
  6. import java.util.List;
  7. import javafx.application.Application;
  8. import javafx.beans.property.SimpleObjectProperty;
  9. import javafx.beans.value.WritableValue;
  10. import javafx.css.CssMetaData;
  11. import javafx.css.Styleable;
  12. import javafx.css.StyleableProperty;
  13. import javafx.css.converter.PaintConverter;
  14. import javafx.geometry.Point2D;
  15. import javafx.scene.Cursor;
  16. import javafx.scene.Scene;
  17. import javafx.scene.control.ContextMenu;
  18. import javafx.scene.control.MenuItem;
  19. import javafx.scene.input.MouseEvent;
  20. import javafx.scene.layout.BorderPane;
  21. import javafx.scene.layout.Region;
  22. import javafx.scene.paint.Color;
  23. import javafx.scene.paint.Paint;
  24. import javafx.scene.shape.Path;
  25. import javafx.scene.shape.PathElement;
  26. import javafx.scene.text.Text;
  27. import javafx.scene.text.TextFlow;
  28. import javafx.stage.Stage;
  29. import org.jspecify.annotations.Nullable;
  30. import util.Resources;
  31.  
  32. public class Launcher extends Application {
  33.  
  34.     static class SelectableText extends TextFlow {
  35.  
  36.         protected final Path wrappingPath = new Path();
  37.         protected SimpleObjectProperty<ContextMenu> contextMenuProperty = new SimpleObjectProperty<>();
  38.  
  39.         protected int mouseDragStartPos = -1;
  40.         protected int selectionStartPos = -1;
  41.         protected int selectionEndPos = -1;
  42.         protected boolean keepFocus;
  43.  
  44.         public SelectableText() {
  45.             super();
  46.  
  47.             setCursor(Cursor.TEXT);
  48.             setPrefWidth(Region.USE_PREF_SIZE);
  49.  
  50.             wrappingPath.setManaged(false);
  51.  
  52.             getStyleClass().add("selectable-text");
  53.             initListeners();
  54.         }
  55.  
  56.         private void initListeners() {
  57.             setOnMousePressed(e -> {
  58.                 if (e.isPopupTrigger()) {
  59.                     showPopupMenu(e);
  60.                     return;
  61.                 }
  62.  
  63.                 var hit = hitTest(new Point2D(e.getX(), e.getY()));
  64.  
  65.                 keepFocus = false;
  66.                 removeSelection();
  67.  
  68.                 if (e.isPrimaryButtonDown() && e.getClickCount() == 2) {
  69.                     // TODO: Double-click selection
  70.                     return;
  71.                 } else {
  72.                     mouseDragStartPos = hit.getCharIndex();
  73.                 }
  74.  
  75.                 // to be able to clear selection on focus lost we should request it first
  76.                 requestFocus();
  77.             });
  78.  
  79.             setOnMouseDragged(e -> {
  80.                 var hit = hitTest(new Point2D(e.getX(), e.getY()));
  81.  
  82.                 selectionStartPos = Math.min(mouseDragStartPos, hit.getCharIndex());
  83.                 selectionEndPos = Math.max(mouseDragStartPos, hit.getCharIndex());
  84.  
  85.                 PathElement[] selectionRange = rangeShape(selectionStartPos, selectionEndPos);
  86.                 wrappingPath.getElements().setAll(selectionRange);
  87.  
  88.                 requestFocus();
  89.             });
  90.  
  91.             setOnMouseReleased(e -> {
  92.                 mouseDragStartPos = -1;
  93.                 if (e.isPopupTrigger()) {
  94.                     showPopupMenu(e);
  95.                 }
  96.             });
  97.  
  98.             contextMenuProperty.addListener((obs, old, val) -> {
  99.                 if (old != null) {
  100.                     old.setOnHidden(null);
  101.                 }
  102.                 if (val != null) {
  103.                     val.setOnHidden(e -> removeSelection());
  104.                 }
  105.             });
  106.  
  107.             // width can be changed by changing the stage size via system shortcuts
  108.             widthProperty().addListener((obs, old, val) -> removeSelection());
  109.  
  110.             // keep selection when context menu is called to perform an action on selected text
  111.             focusWithinProperty().addListener((obs, old, val) -> {
  112.                 if (!val && !keepFocus) {
  113.                     removeSelection();
  114.                 }
  115.             });
  116.         }
  117.  
  118.         public void setText(Text... text) {
  119.             getChildren().setAll(wrappingPath);
  120.             getChildren().addAll(text);
  121.         }
  122.  
  123.         public void clear() {
  124.             getChildren().setAll(wrappingPath);
  125.         }
  126.  
  127.         public @Nullable String getSelectedTextAsString() {
  128.             var content = getTextFlowContentAsString();
  129.             return selectionStartPos > 0 && selectionEndPos > selectionStartPos
  130.                 ? content.substring(selectionStartPos, selectionEndPos)
  131.                 : null;
  132.         }
  133.  
  134.         public ContextMenu getContextMenu() {
  135.             return contextMenuProperty.get();
  136.         }
  137.  
  138.         public SimpleObjectProperty<ContextMenu> contextMenuProperty() {
  139.             return contextMenuProperty;
  140.         }
  141.  
  142.         public void setContextMenu(@Nullable ContextMenu contextMenu) {
  143.             this.contextMenuProperty.set(contextMenu);
  144.         }
  145.  
  146.         protected StringBuilder getTextFlowContentAsString() {
  147.             var sb = new StringBuilder();
  148.             for (var node : getChildren()) {
  149.                 if (node instanceof Text t) {
  150.                     sb.append(t.getText());
  151.                 }
  152.             }
  153.             return sb;
  154.         }
  155.  
  156.         protected void removeSelection() {
  157.             selectionStartPos = -1;
  158.             selectionEndPos = -1;
  159.             wrappingPath.getElements().clear();
  160.         }
  161.  
  162.         protected void showPopupMenu(MouseEvent e) {
  163.             var contextMenu = contextMenuProperty.get();
  164.             if (contextMenu != null) {
  165.                 keepFocus = true;
  166.                 contextMenu.show(this, e.getScreenX(), e.getScreenY());
  167.             }
  168.         }
  169.  
  170.         ///////////////////////////////////////////////////////////////////////////
  171.         // Styleable Properties                                                  //
  172.         ///////////////////////////////////////////////////////////////////////////
  173.  
  174.         private static class StyleableProperties {
  175.  
  176.             private static final CssMetaData<SelectableText, Paint> HIGHLIGHT_FILL = new CssMetaData<>(
  177.                 "-fx-highlight-fill", PaintConverter.getInstance(), Color.TRANSPARENT
  178.             ) {
  179.  
  180.                 @Override
  181.                 public boolean isSettable(SelectableText c) {
  182.                     return c.wrappingPath.fillProperty() == null || !c.wrappingPath.fillProperty().isBound();
  183.                 }
  184.  
  185.                 @Override
  186.                 public StyleableProperty<Paint> getStyleableProperty(SelectableText c) {
  187.                     var val = (WritableValue<Paint>) c.wrappingPath.fillProperty();
  188.                     return (StyleableProperty<Paint>) val;
  189.                 }
  190.             };
  191.  
  192.             private static final CssMetaData<SelectableText, Paint> HIGHLIGHT_STROKE = new CssMetaData<>(
  193.                 "-fx-highlight-stroke", PaintConverter.getInstance(), Color.TRANSPARENT
  194.             ) {
  195.  
  196.                 @Override
  197.                 public boolean isSettable(SelectableText c) {
  198.                     return c.wrappingPath.strokeProperty() == null || !c.wrappingPath.strokeProperty().isBound();
  199.                 }
  200.  
  201.                 @Override
  202.                 public StyleableProperty<Paint> getStyleableProperty(SelectableText c) {
  203.                     var val = (WritableValue<Paint>) c.wrappingPath.strokeProperty();
  204.                     return (StyleableProperty<Paint>) val;
  205.                 }
  206.             };
  207.  
  208.             private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
  209.  
  210.             static {
  211.                 final List<CssMetaData<? extends Styleable, ?>> styleables =
  212.                     new ArrayList<>(TextFlow.getClassCssMetaData());
  213.                 styleables.add(HIGHLIGHT_FILL);
  214.                 styleables.add(HIGHLIGHT_STROKE);
  215.                 STYLEABLES = Collections.unmodifiableList(styleables);
  216.             }
  217.         }
  218.  
  219.         public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
  220.             return StyleableProperties.STYLEABLES;
  221.         }
  222.  
  223.         @Override
  224.         public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
  225.             return getClassCssMetaData();
  226.         }
  227.     }
  228.  
  229.     public static void main(String[] args) {
  230.         launch();
  231.     }
  232.  
  233.     @Override
  234.     public void start(Stage stage) {
  235.         var st = new SelectableText();
  236.         st.getStylesheets().add(Styles.toDataURI(
  237.             """
  238.                .selectable-text {
  239.                    -fx-highlight-fill: rgba(255, 0, 0, 0.25);
  240.                    -fx-highlight-stroke: rgba(255, 0, 0, 0.25);
  241.                }
  242.                """
  243.         ));
  244.         st.setText(
  245.             new Text("Lorem Ipsum ".repeat(20))
  246.         );
  247.  
  248.         var copy = new MenuItem("Copy");
  249.         copy.setOnAction(e -> System.out.println(st.getSelectedTextAsString()));
  250.  
  251.         var menu = new ContextMenu();
  252.         menu.getItems().add(copy);
  253.         st.setContextMenu(menu);
  254.  
  255.         var root = new BorderPane(st);
  256.  
  257.         var scene = new Scene(root, 800, 200);
  258.         scene.getStylesheets().add(Resources.getResource("/index.css"));
  259.         stage.setScene(scene);
  260.         stage.show();
  261.     }
  262. }
  263.  
Tags: JavaFX
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement