import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

/**
 * Mouse and key events, with focus. Everything prints to the console.
 *
 * Three label "pads", A, B and C. Each one reports the mouse and key events it receives.
 *
 *   - Mouse events go to the node under the pointer.
 *   - Key events go only to the node that has FOCUS.
 *   - Clicking a Label does not give it focus. The mouse handler calls requestFocus() to do that.
 *   - requestFocus() moves focus from code. The "Focus B" button does it too.
 *   - setFocusTraversable(true) puts a node in the Tab / arrow-key order. A and B are in it,
 *     C is not, so Tab skips C. (C can still get focus when you click it, because the handler asks.)
 *
 * Things to try: click a pad and type; press Tab and watch where focus goes; click C and type;
 * press "Focus B"; press "Let C join Tab" and then Tab around again.
 * The focused pad is drawn with a blue border.
 */

public class SimpleFocusDemo extends Application {

	private Scene scene;

	// A pad: a Label that reports the mouse and key events it gets.
	private Label pad ( String name, String color ) {
		Label pad = new Label(name);
		pad.setPrefSize(110,70);
		pad.setAlignment(Pos.CENTER);
		pad.setStyle("-fx-background-color: " + color + ";");
		pad.getStyleClass().add("pad"); // the border comes from the style sheet
		                                // below

		pad.setOnMousePressed(e -> {
			System.out
			    .println(name + ": " + e.getEventType() + " at " + (int) e.getX()
			        + ", " + (int) e.getY() + "  button " + e.getButton());
			pad.requestFocus(); // clicking a Label does not focus it, so ask for
			                    // focus here
			printFocusOwner();
		});
		pad.setOnMouseClicked(e -> System.out.println(name + ": " + e.getEventType()
		    + "  clicks " + e.getClickCount()));
		pad.setOnKeyPressed(e -> {
			System.out.println(name + ": " + e.getEventType() + "  code "
			    + e.getCode() + "  shift " + e.isShiftDown());
			if ( e.getCode() == KeyCode.TAB ) {
				Platform.runLater(this::printFocusOwner); // Tab moves focus after this
				                                          // handler, so look a moment
				                                          // later
			}
		});
		pad.setOnKeyTyped(e -> System.out.println(name + ": " + e.getEventType()
		    + "  character '" + e.getCharacter() + "'"));
		return pad;
	}

	// The focus border needs a style sheet (a :focused rule cannot be written
	// with setStyle).
	// To keep this demo to one file, the style sheet is written to a temporary
	// file.
	private void useStyleSheet () {
		try {
			Path css = Files.createTempFile("mousekey",".css");
			Files.writeString(css,".pad { -fx-border-color: #333; }\n"
			    + ".pad:focused { -fx-border-color: dodgerblue; -fx-border-width: 3; }\n");
			css.toFile().deleteOnExit();
			scene.getStylesheets().add(css.toUri().toString());
		} catch ( IOException e ) {
			System.out
			    .println("could not write the style sheet; the focus border will not show");
		}
	}

	private void printFocusOwner () {
		System.out.println("focus owner: " + describe(scene.getFocusOwner()));
	}

	private String describe ( Object node ) {
		if ( node instanceof Label ) {
			return "pad " + ((Label) node).getText();
		}
		if ( node instanceof Button ) {
			return "button \"" + ((Button) node).getText() + "\"";
		}
		return String.valueOf(node);
	}

	@Override
	public void start ( Stage stage ) {
		Label a = pad("A","#cfe8ff");
		Label b = pad("B","#c8f0c8");
		Label c = pad("C","#ffe0b3");

		// ---- setFocusTraversable: is this node in the Tab / arrow-key order? ----
		a.setFocusTraversable(true); // a Label is not in the Tab order unless you
		                             // say so
		b.setFocusTraversable(true);
		// c is left alone, so Tab skips it
		// try: c.setFocusTraversable(true);

		// ---- requestFocus: move focus from code ----
		Button focusB = new Button("Focus B");
		focusB.setOnAction(e -> {
			b.requestFocus(); // the button took focus when clicked; this moves it to
			                  // B
			printFocusOwner();
		});

		Button letCFocus = new Button("Let C join Tab");
		letCFocus.setOnAction(e -> {
			c.setFocusTraversable(true);
			System.out
			    .println("C is now in the Tab order (click a pad, then press Tab)");
		});

		HBox pads = new HBox(15,a,b,c);
		HBox buttons = new HBox(15,focusB,letCFocus);
		VBox root =
		    new VBox(20,
		             new Label("Click a pad and type, or press Tab. Watch the console."),
		             pads,buttons);
		root.setPadding(new Insets(20));

		scene = new Scene(root,460,220);
		useStyleSheet(); // draws the focused pad with a thick blue border

		stage.setScene(scene);
		stage.setTitle("Mouse, keys and focus");
		stage.show();

		a.requestFocus(); // start with A focused (needs the stage to be showing)
		printFocusOwner();
	}

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