Last active
July 10, 2019 15:07
-
-
Save nickthecoder/57ac20829214f1209e41e851d955ed35 to your computer and use it in GitHub Desktop.
JavaFX : Move keyboard focus to the next Node. (Written in Kotlin)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package uk.co.nickthecoder.tedi | |
| import javafx.scene.Node | |
| import javafx.scene.Parent | |
| /** | |
| * JavaFX 8 does not expose an API to navigate focus. This is a "bodge" to get around the problem. | |
| * Attempts to request focus on the next node. | |
| * | |
| * Note, the ordering is based on order of the children within [Parent] nodes, and may not be the | |
| * same order as JavaFX's normal navigation. | |
| * | |
| * @param attempts - In case there is a bug, prevent infinite loops by stopping after checking this many nodes. | |
| * | |
| * I've added this as a github gist : | |
| * https://gist.github.com/nickthecoder/57ac20829214f1209e41e851d955ed35 | |
| */ | |
| fun Node.focusNext(attempts: Int = 1000) = FocusNext(this, attempts).start() | |
| /** | |
| * JavaFX 8 does not expose an API to navigate focus. This is a "bodge" to get around the problem. | |
| * Attempts to request focus on the previous node. | |
| * | |
| * Note, the ordering is based on order of the children within [Parent] nodes, and may not be the | |
| * same order as JavaFX's normal navigation. | |
| * | |
| * @param attempts - In case there is a bug, prevent infinite loops by stopping after checking this many nodes. | |
| */ | |
| fun Node.focusPrevious(attempts: Int = 1000) = FocusPrevious(this, attempts).start() | |
| /** | |
| * Look at all later siblings one at a time. | |
| * If it isFocusTraversable and visible and not disabled, then we are done!. | |
| * If it is a parent, visible and not disabled, then we also need to check its | |
| * children (recursively) before moving onto the next sibling. | |
| * | |
| * If we reach the end of the later siblings, then we need to look to our parent, | |
| * and check its later siblings (in the same manner, recursively into its children). | |
| * Again, if we find one that is focusTraversable, visible and not disabled, then we are done. | |
| * | |
| * If we reach the top-most parent, without success, then we need to start | |
| * from its first child, working forwards as before. | |
| * Note. The first child will not have been checked yet, because so far, we have only been | |
| * checking later siblings and their descendants. | |
| * | |
| * Note. It is quite possible to loop around the whole scene tree, and end up where we started. | |
| * In which case we stop without doing anything. | |
| * | |
| * Also, in case this code is buggy, if we check too many nodes (the default is 1000), then | |
| * we give up. | |
| * | |
| * All methods return true on success or the max attempts is exceeded, | |
| * and false if further searching is required.. | |
| */ | |
| private class FocusNext(val startNode: Node, var attempts: Int) { | |
| fun tryAllChildren(parent: Parent): Boolean { | |
| if (--attempts <= 0) return true | |
| for (child in parent.childrenUnmodifiable) { | |
| if (tryOneNodeRecursively(child)) { | |
| return true | |
| } | |
| } | |
| return false | |
| } | |
| fun tryOneNodeRecursively(node: Node): Boolean { | |
| if (--attempts <= 0) return true | |
| if (node === startNode) { | |
| attempts = 0 | |
| return true | |
| } | |
| if (node.isFocusTraversable && node.isVisible && !node.isDisabled) { | |
| node.requestFocus() | |
| return true | |
| } | |
| if (node is Parent && node.isVisible && !node.isDisabled) { | |
| if (tryAllChildren(node)) { | |
| return true | |
| } | |
| } | |
| return false | |
| } | |
| /** | |
| * | |
| * Only looks at later siblings from [node]. | |
| */ | |
| fun tryFromNode(node: Node): Boolean { | |
| val parent = node.parent | |
| parent ?: return false | |
| val children = parent.childrenUnmodifiable | |
| val idx = children.indexOf(node) | |
| if (idx >= 0) { | |
| for (i in idx + 1..children.size - 1) { | |
| if (tryOneNodeRecursively(children[i])) { | |
| return true | |
| } | |
| } | |
| } | |
| // Now look at PARENT's later siblings | |
| if (tryFromNode(parent)) { | |
| return true | |
| } | |
| return false | |
| } | |
| fun start(): Boolean { | |
| if (tryFromNode(startNode)) { | |
| return true | |
| } | |
| var topMost = startNode | |
| while (topMost.parent != null) { | |
| topMost = topMost.parent | |
| } | |
| if (topMost is Parent) { | |
| return tryAllChildren(topMost) | |
| } | |
| return false | |
| } | |
| } | |
| /** | |
| * This is the same as FocusNext, except it looks at EARLIER siblings. | |
| */ | |
| private class FocusPrevious(val startNode: Node, var attempts: Int) { | |
| fun tryAllChildren(parent: Parent): Boolean { | |
| if (--attempts <= 0) return true | |
| for (child in parent.childrenUnmodifiable.reversed()) { | |
| if (tryOneNodeRecursively(child)) { | |
| return true | |
| } | |
| } | |
| return false | |
| } | |
| // This method is identical in both classes. Should I create a base class? | |
| fun tryOneNodeRecursively(node: Node): Boolean { | |
| if (--attempts <= 0) return true | |
| if (node === startNode) { | |
| attempts = 0 | |
| return true | |
| } | |
| if (node.isFocusTraversable && node.isVisible && !node.isDisabled) { | |
| node.requestFocus() | |
| return true | |
| } | |
| if (node is Parent && node.isVisible && !node.isDisabled) { | |
| if (tryAllChildren(node)) { | |
| return true | |
| } | |
| } | |
| return false | |
| } | |
| fun tryFromNode(node: Node): Boolean { | |
| val parent = node.parent | |
| parent ?: return false | |
| val children = parent.childrenUnmodifiable | |
| val idx = children.indexOf(node) | |
| if (idx >= 1) { | |
| for (i in idx - 1 downTo 0) { | |
| if (tryOneNodeRecursively(children[i])) { | |
| return true | |
| } | |
| } | |
| } | |
| // Now look at PARENT's earlier siblings | |
| if (tryFromNode(parent)) { | |
| return true | |
| } | |
| return false | |
| } | |
| fun start(): Boolean { | |
| if (tryFromNode(startNode)) { | |
| return true | |
| } | |
| var topMost = startNode | |
| while (topMost.parent != null) { | |
| topMost = topMost.parent | |
| } | |
| if (topMost is Parent) { | |
| return tryAllChildren(topMost) | |
| } | |
| return false | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment