Событие освобождения мыши не происходит в Ubuntu, когда целевой Node меняет сцену? - PullRequest
0 голосов
/ 08 мая 2018

Этот вопрос касается поведения мыши в операционных системах; в частности, мой код работает в Windows и Mac OS X, но не в Ubuntu.

В конечном итоге я пытаюсь создать специальный подкласс Pane («ConvertiblePane»), который существует в родительской панели на главной сцене / сцене, но волшебным образом переносится на собственную временную сцену / сцену при перетаскивании, таким образом становясь независимый и может быть размещен в любом месте на экране. Когда пользователь отпускает кнопку мыши, ConvertiblePane должен вернуться на исходную родительскую панель и потерять временную стадию. (В моей полной программе исходная родительская сцена изменяет размеры / перемещает себя, чтобы приспособить конвертируемую панель везде, где она была отброшена.)

Это подводит меня к моей проблеме. Когда я нажимаю мышь на ConvertiblePane, он запускает MousePress в главной сцене, как и ожидалось, и в этот момент ConvertiblePane перемещается на временную стадию. Когда я перетаскиваю мышь, она вызывает MouseDrag во временной сцене и перемещает временную сцену. Хорошо, отлично.

Однако, когда я отпускаю кнопку мыши, в разных операционных системах у меня разное поведение. В Windows (7) и Mac OS X (10.12.6) в временной сцене происходит MouseRelease, отправляя панель обратно своему первоначальному родительскому элементу на основной стадии, как и ожидалось. Однако в Ubuntu MouseRelease не создается ни в основной, ни в временной сцене.

Вот соответствующий код в качестве примера MCV:

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.stage.StageStyle;

public class ConvertibleTest extends Application {           

    @Override
    public void start(Stage primaryStage) {

        // Set up the main stage and scene with a
        // Pane as root and a ConvertiblePane child:
        primaryStage.initStyle(StageStyle.TRANSPARENT);        
        Pane root = new Pane();
        ConvertiblePane conv = new ConvertiblePane();
        root.getChildren().add(conv);        
        Scene scene = new Scene(root, 400, 400, Color.PINK);
        primaryStage.setTitle("Convertible Test");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }    
}

class ConvertiblePane extends Pane
{    
    private final Group TEMP_ROOT = new Group();
    private final Stage TEMP_STAGE = new Stage(StageStyle.TRANSPARENT);    
    private final Scene TEMP_SCENE = new Scene(TEMP_ROOT);
    private Pane originalParent = null;
    private double deltaX = 0.0;
    private double deltaY = 0.0;
    private String name = null;

    public void onMousePress(MouseEvent event)
    {
        // Save deltaX/Y for later:
        Point2D delta = this.sceneToLocal(event.getX(), event.getY());
        deltaX = delta.getX();
        deltaY = delta.getY();  

        if (!isIndependent())
        {
            makeIndependent();
        }
    }

    public void onMouseDrag(MouseEvent event)
    {
        // Keep the TEMP_STAGE relative to the original click point:
        TEMP_STAGE.setX(event.getScreenX()-deltaX);
        TEMP_STAGE.setY(event.getScreenY()-deltaY);
    }

    public void onMouseRelease(MouseEvent event)
    {
        if (isIndependent())
        {
            returnToParent();
        }
    }

    public ConvertiblePane()
    {                         
        this.setPrefSize(100, 100);
        this.setBackground(new Background(new BackgroundFill(Color.GREEN, new CornerRadii(10), Insets.EMPTY)));
        this.setVisible(true);

        // Attach event code and report to System.out what is happening:
        this.setOnMousePressed((MouseEvent event) -> {
            if (this.getScene() == TEMP_SCENE)
                System.out.println("Pressed as Independent");
            else
                System.out.println("Pressed as Child");
            onMousePress(event);
        });
        this.setOnMouseDragged((MouseEvent event) -> {
            if (this.getScene() == TEMP_SCENE)
                System.out.println("Dragged as Independent");
            else
                System.out.println("Dragged as Child");
            onMouseDrag(event);
        });
        this.setOnMouseReleased((MouseEvent event) -> {
            if (this.getScene() == TEMP_SCENE)
                System.out.println("Released as Independent");
            else
                System.out.println("Released as Child");
            onMouseRelease(event);
        });
    }

    public boolean isIndependent()
    {
        // Return whether this ConvertiblePane is "independent" (exists in its own temp scene)
        return this.getScene() == TEMP_SCENE;
    }

    public void makeIndependent()
    {                
        // Get the point where this ConvertiblePane appears on screen:
        Point2D screenPt = this.localToScreen(0, 0);

        // Save the originaParent of this ConvertiblePane; we will return to it later:
        originalParent = (Pane)getParent();

        // Remove this ConvertiblePane from its originalParent:
        originalParent.getChildren().remove(this);

        // Set this ConvertiblePane as the root of the TEMP_SCENE on the TEMP_STAGE:
        TEMP_SCENE.setRoot(this);        
        TEMP_STAGE.setScene(TEMP_SCENE);
        System.out.println("Transferred to TEMP.");
        this.relocate(0, 0);

        // Show the TEMP_STAGE in the same location on screen where this ConvertiblePane originally was:
        TEMP_STAGE.setX(screenPt.getX());
        TEMP_STAGE.setY(screenPt.getY());                                                   
        TEMP_STAGE.show();        
    }

    public void returnToParent()
    {
        // Reset deltas:
        deltaX = 0;
        deltaY = 0;

        // Get the location of this ConvertiblePane on screen:
        Point2D screenPt = this.localToScreen(0, 0);

        // Set TEMP_ROOT as the root of TEMP_SCENE; this will allow us to detach
        // this ConvertiblePane from being the scene root (since root cannot == null).
        TEMP_SCENE.setRoot(TEMP_ROOT);

        // Hide the TEMP_STAGE:
        TEMP_STAGE.hide();        

        // Add this ConvertiblePane back to the originalParent:
        originalParent.getChildren().add(this);
        System.out.println("Transferred to MAIN.");

        // Relocate this ConvertiblePane within the originalParent to maintain its position on screen
        Point2D parentPt = originalParent.screenToLocal(screenPt);
        this.relocate(parentPt.getX(), parentPt.getY());          
    }
}

Как видите, в методах обработки событий есть несколько базовых отчетов; и методы makeIndependent () и returnToParent () выводят «Передано в TEMP». и "Переведено в ГЛАВНУЮ". соответственно.

Если я щелкну мышью на ConvertiblePane, перетащу несколько пикселей и отпущу, это будет вывод:

(on Windows or Mac OS X) Pressed as Child Transferred to TEMP. Dragged as Independent Dragged as Independent Dragged as Independent Released as Independent Transferred to MAIN.

(on Ubuntu) Pressed as Child Transferred to TEMP. Dragged as Independent Dragged as Independent Dragged as Independent

Я также попытался добавить фильтры событий к двум сценам; но результат тот же: MouseRelease происходит на Win / Mac, но не на Ubuntu.

Если кто-то может объяснить это поведение или предложить что-то, это было бы здорово. В качестве альтернативы ... есть ли какое-нибудь глобальное (до сцены) создание MouseEvents, которое я мог бы поймать? Я имею в виду, меня не волнуют детали выпуска мыши; Я просто хочу, чтобы событие сообщило мне, когда добавить ConvertiblePane обратно на главную сцену.

Спасибо!

1 Ответ

0 голосов
/ 23 мая 2018

Потратив на это несколько недель, я не смог найти способ вызвать надлежащее событие MouseReleased в Ubuntu для этой ситуации; Тем не менее, я придумал хак, который делает работу достаточно хорошо. По сути, вместо того, чтобы получать уведомление о возникновении MouseReleased, я проверяю каждые 10 миллисекунд, чтобы убедиться, что кнопка мыши больше не нажата.

Объяснение: Когда узел переносится во временную сцену, временная шкала начинает «перемещать» указатель мыши на месте каждые 10 миллисекунд. Это вызывает либо событие MouseDragged (если кнопка мыши все еще нажата), либо событие MouseMoved (если кнопка мыши нажата); поэтому я могу смоделировать событие MouseReleased и вызвать мою процедуру для добавления узла обратно на основную стадию. В этот момент, конечно, я также останавливаю Временную шкалу.

Вот соответствующий код, чтобы продемонстрировать это; возможно, это будет полезно и кому-то еще.

// The robot is needed to "move" the mouse in place,
// triggering a MOUSE_MOVED event.
private static Robot robot = null;  
static {
    try {
        robot = new Robot();
    } catch (AWTException ex) {
        Logger.getLogger(ConvertiblePane.class.getName()).log(Level.SEVERE, null, ex);
    }
}

// clickWaiter will move the mouse in place every 10 milliseconds,
// triggering a MOUSE_MOVED event if the mouse is no longer pressed.
private final Timeline clickWaiter = new Timeline(new KeyFrame(Duration.millis(10), (ActionEvent event) -> {
    // Get the mouse position from MouseInfo class.
    Point mouse = MouseInfo.getPointerInfo().getLocation();        
    // "Move" the mouse in place to trigger a mouseMove or mouseDrag event.
    robot.mouseMove(mouse.x, mouse.y);
}));

public ConvertiblePane()
{
    ...

    // MOUSE_MOVED will be triggered by the robot when the mouse button is no longer pressed:
    TEMP_SCENE.addEventFilter(MouseEvent.MOUSE_MOVED, (MouseEvent event) ->
    {   
        if (!event.isPrimaryButtonDown())
        {
            System.out.println("Temp.release (sim)");
            clickWaiter.stop();
            // Simulate MOUSE_RELEASED event.
        }
    });
}

public void makeIndependent()
{
    ...

    // Start the clickWaiter, part of the Linux hack:
    clickWaiter.playFromStart();
}
...