Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- package playground;
- import atlantafx.base.theme.Styles;
- import java.util.ArrayList;
- import java.util.Collections;
- import java.util.List;
- import javafx.application.Application;
- import javafx.beans.property.SimpleObjectProperty;
- import javafx.beans.value.WritableValue;
- import javafx.css.CssMetaData;
- import javafx.css.Styleable;
- import javafx.css.StyleableProperty;
- import javafx.css.converter.PaintConverter;
- import javafx.geometry.Point2D;
- import javafx.scene.Cursor;
- import javafx.scene.Scene;
- import javafx.scene.control.ContextMenu;
- import javafx.scene.control.MenuItem;
- import javafx.scene.input.MouseEvent;
- import javafx.scene.layout.BorderPane;
- import javafx.scene.layout.Region;
- import javafx.scene.paint.Color;
- import javafx.scene.paint.Paint;
- import javafx.scene.shape.Path;
- import javafx.scene.shape.PathElement;
- import javafx.scene.text.Text;
- import javafx.scene.text.TextFlow;
- import javafx.stage.Stage;
- import org.jspecify.annotations.Nullable;
- import util.Resources;
- public class Launcher extends Application {
- static class SelectableText extends TextFlow {
- protected final Path wrappingPath = new Path();
- protected SimpleObjectProperty<ContextMenu> contextMenuProperty = new SimpleObjectProperty<>();
- protected int mouseDragStartPos = -1;
- protected int selectionStartPos = -1;
- protected int selectionEndPos = -1;
- protected boolean keepFocus;
- public SelectableText() {
- super();
- setCursor(Cursor.TEXT);
- setPrefWidth(Region.USE_PREF_SIZE);
- wrappingPath.setManaged(false);
- getStyleClass().add("selectable-text");
- initListeners();
- }
- private void initListeners() {
- setOnMousePressed(e -> {
- if (e.isPopupTrigger()) {
- showPopupMenu(e);
- return;
- }
- var hit = hitTest(new Point2D(e.getX(), e.getY()));
- keepFocus = false;
- removeSelection();
- if (e.isPrimaryButtonDown() && e.getClickCount() == 2) {
- // TODO: Double-click selection
- return;
- } else {
- mouseDragStartPos = hit.getCharIndex();
- }
- // to be able to clear selection on focus lost we should request it first
- requestFocus();
- });
- setOnMouseDragged(e -> {
- var hit = hitTest(new Point2D(e.getX(), e.getY()));
- selectionStartPos = Math.min(mouseDragStartPos, hit.getCharIndex());
- selectionEndPos = Math.max(mouseDragStartPos, hit.getCharIndex());
- PathElement[] selectionRange = rangeShape(selectionStartPos, selectionEndPos);
- wrappingPath.getElements().setAll(selectionRange);
- requestFocus();
- });
- setOnMouseReleased(e -> {
- mouseDragStartPos = -1;
- if (e.isPopupTrigger()) {
- showPopupMenu(e);
- }
- });
- contextMenuProperty.addListener((obs, old, val) -> {
- if (old != null) {
- old.setOnHidden(null);
- }
- if (val != null) {
- val.setOnHidden(e -> removeSelection());
- }
- });
- // width can be changed by changing the stage size via system shortcuts
- widthProperty().addListener((obs, old, val) -> removeSelection());
- // keep selection when context menu is called to perform an action on selected text
- focusWithinProperty().addListener((obs, old, val) -> {
- if (!val && !keepFocus) {
- removeSelection();
- }
- });
- }
- public void setText(Text... text) {
- getChildren().setAll(wrappingPath);
- getChildren().addAll(text);
- }
- public void clear() {
- getChildren().setAll(wrappingPath);
- }
- public @Nullable String getSelectedTextAsString() {
- var content = getTextFlowContentAsString();
- return selectionStartPos > 0 && selectionEndPos > selectionStartPos
- ? content.substring(selectionStartPos, selectionEndPos)
- : null;
- }
- public ContextMenu getContextMenu() {
- return contextMenuProperty.get();
- }
- public SimpleObjectProperty<ContextMenu> contextMenuProperty() {
- return contextMenuProperty;
- }
- public void setContextMenu(@Nullable ContextMenu contextMenu) {
- this.contextMenuProperty.set(contextMenu);
- }
- protected StringBuilder getTextFlowContentAsString() {
- var sb = new StringBuilder();
- for (var node : getChildren()) {
- if (node instanceof Text t) {
- sb.append(t.getText());
- }
- }
- return sb;
- }
- protected void removeSelection() {
- selectionStartPos = -1;
- selectionEndPos = -1;
- wrappingPath.getElements().clear();
- }
- protected void showPopupMenu(MouseEvent e) {
- var contextMenu = contextMenuProperty.get();
- if (contextMenu != null) {
- keepFocus = true;
- contextMenu.show(this, e.getScreenX(), e.getScreenY());
- }
- }
- ///////////////////////////////////////////////////////////////////////////
- // Styleable Properties //
- ///////////////////////////////////////////////////////////////////////////
- private static class StyleableProperties {
- private static final CssMetaData<SelectableText, Paint> HIGHLIGHT_FILL = new CssMetaData<>(
- "-fx-highlight-fill", PaintConverter.getInstance(), Color.TRANSPARENT
- ) {
- @Override
- public boolean isSettable(SelectableText c) {
- return c.wrappingPath.fillProperty() == null || !c.wrappingPath.fillProperty().isBound();
- }
- @Override
- public StyleableProperty<Paint> getStyleableProperty(SelectableText c) {
- var val = (WritableValue<Paint>) c.wrappingPath.fillProperty();
- return (StyleableProperty<Paint>) val;
- }
- };
- private static final CssMetaData<SelectableText, Paint> HIGHLIGHT_STROKE = new CssMetaData<>(
- "-fx-highlight-stroke", PaintConverter.getInstance(), Color.TRANSPARENT
- ) {
- @Override
- public boolean isSettable(SelectableText c) {
- return c.wrappingPath.strokeProperty() == null || !c.wrappingPath.strokeProperty().isBound();
- }
- @Override
- public StyleableProperty<Paint> getStyleableProperty(SelectableText c) {
- var val = (WritableValue<Paint>) c.wrappingPath.strokeProperty();
- return (StyleableProperty<Paint>) val;
- }
- };
- private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
- static {
- final List<CssMetaData<? extends Styleable, ?>> styleables =
- new ArrayList<>(TextFlow.getClassCssMetaData());
- styleables.add(HIGHLIGHT_FILL);
- styleables.add(HIGHLIGHT_STROKE);
- STYLEABLES = Collections.unmodifiableList(styleables);
- }
- }
- public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
- return StyleableProperties.STYLEABLES;
- }
- @Override
- public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
- return getClassCssMetaData();
- }
- }
- public static void main(String[] args) {
- launch();
- }
- @Override
- public void start(Stage stage) {
- var st = new SelectableText();
- st.getStylesheets().add(Styles.toDataURI(
- """
- .selectable-text {
- -fx-highlight-fill: rgba(255, 0, 0, 0.25);
- -fx-highlight-stroke: rgba(255, 0, 0, 0.25);
- }
- """
- ));
- st.setText(
- new Text("Lorem Ipsum ".repeat(20))
- );
- var copy = new MenuItem("Copy");
- copy.setOnAction(e -> System.out.println(st.getSelectedTextAsString()));
- var menu = new ContextMenu();
- menu.getItems().add(copy);
- st.setContextMenu(menu);
- var root = new BorderPane(st);
- var scene = new Scene(root, 800, 200);
- scene.getStylesheets().add(Resources.getResource("/index.css"));
- stage.setScene(scene);
- stage.show();
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement