{"id":28410061,"url":"https://github.com/fxmisc/wellbehavedfx","last_synced_at":"2025-12-25T00:01:06.870Z","repository":{"id":21128904,"uuid":"24429801","full_name":"FXMisc/WellBehavedFX","owner":"FXMisc","description":"Composable event handlers and skin scaffolding for JavaFX controls.","archived":false,"fork":false,"pushed_at":"2018-07-16T15:09:30.000Z","size":143,"stargazers_count":60,"open_issues_count":4,"forks_count":8,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-06-02T21:13:36.888Z","etag":null,"topics":["behavior","controller","javafx"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/FXMisc.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-09-24T19:41:32.000Z","updated_at":"2025-01-22T03:06:02.000Z","dependencies_parsed_at":"2022-08-27T00:10:40.955Z","dependency_job_id":null,"html_url":"https://github.com/FXMisc/WellBehavedFX","commit_stats":null,"previous_names":["tomasmikula/wellbehavedfx"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/FXMisc/WellBehavedFX","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FXMisc%2FWellBehavedFX","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FXMisc%2FWellBehavedFX/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FXMisc%2FWellBehavedFX/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FXMisc%2FWellBehavedFX/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/FXMisc","download_url":"https://codeload.github.com/FXMisc/WellBehavedFX/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FXMisc%2FWellBehavedFX/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261385953,"owners_count":23150829,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["behavior","controller","javafx"],"created_at":"2025-06-02T11:10:48.953Z","updated_at":"2025-12-25T00:01:06.767Z","avatar_url":"https://github.com/FXMisc.png","language":"Java","readme":"**This project is no longer being maintained. See [this issue](https://github.com/FXMisc/WellBehavedFX/issues/22) for more details.**\n\nWellBehavedFX\n=============\n\nThis project provides a better mechanism for defining and overriding event handlers (e.g. keyboard shortcuts) for JavaFX. Such mechanism, also known as InputMap API, was considered as part of [JEP 253](http://openjdk.java.net/jeps/253) (see also [JDK-8076423](https://bugs.openjdk.java.net/browse/JDK-8076423)), but was dropped. (I guess I was the most vocal opponent of the proposal ([link to discussion thread](http://mail.openjdk.java.net/pipermail/openjfx-dev/2015-August/017667.html)).)\n\n\nUse Cases\n---------\n\n### Event Matching ###\n\nUse cases in this section focus on expressivity of event matching, i.e. expressing what events should be handled.\n\n#### Key Combinations ####\nThe task is to add handlers for the following key combinations to `Node node`:\n\n| Key Combination | Comment | Handler |\n| --------------- | ------- | ------- |\n| \u003ckbd\u003eEnter\u003c/kbd\u003e | no modifier keys pressed | `enterPressed()` |\n| [\u003ckbd\u003eShift\u003c/kbd\u003e+]\u003ckbd\u003eA\u003c/kbd\u003e | optional \u003ckbd\u003eShift\u003c/kbd\u003e, no other modifiers | `aPressed()` |\n| \u003ckbd\u003eShortcut\u003c/kbd\u003e+\u003ckbd\u003eShift\u003c/kbd\u003e+\u003ckbd\u003eS\u003c/kbd\u003e |  | `saveAll()` |\n\n```java\nNodes.addInputMap(node, sequence(\n    consume(keyPressed(ENTER),                        e -\u003e enterPressed()),\n    consume(keyPressed(A, SHIFT_ANY),                 e -\u003e aPressed()),\n    consume(keyPressed(S, SHORTCUT_DOWN, SHIFT_DOWN), e -\u003e saveAll())\n));\n```\n\n#### Same action for different events ####\nIn some situations it is desirable to bind multiple different events to the same action. Task: Invoke `action()` when a `Button` is either left-clicked or \u003ckbd\u003eSpace\u003c/kbd\u003e-pressed. If these are the only two events handled by the button, one handler for each of `MOUSE_CLICKED` and `KEY_PRESSED` event types will be installed on the button (as opposed to, for example, installing a common handler for the nearest common supertype, which in this case would be `InputEvent.ANY`).\n\n```java\nNodes.addInputMap(button, consume(\n        anyOf(mouseClicked(PRIMARY), keyPressed(SPACE)),\n        e -\u003e action()));\n```\n\n#### Text Input ####\nHandle text input, i.e. `KEY_TYPED` events, _except_ for the new line character, which should be left unconsumed. In this example, echo the input to standard output.\n\n```java\nNodes.addInputMap(button, consume(\n        keyTyped(c -\u003e !c.equals(\"\\n\")),\n        e -\u003e System.out.print(e.getCharacter())));\n```\n\n#### Custom Events ####\nAssume the following custom event declaration:\n\n```java\nclass FooEvent extends Event {\n    public static final EventType\u003cFooEvent\u003e FOO;\n\n    public boolean isSecret();\n    public String getValue();\n}\n```\n\nThe task is to print out the value of and consume non-secret `Foo` events of `node`. Secret `Foo` events should be left unconsumed, i.e. let to bubble up.\n\n```java\nNodes.addInputMap(node, consume(\n        eventType(FooEvent.FOO).unless(FooEvent::isSecret),\n        e -\u003e System.out.print(e.getValue())));\n```\n\n\n### Manipulating Input Mappings ###\n\nUse cases in this section focus on manipulating input mappings of a control, such as overriding mappings, adding default mappings, intercepting mappings, removing a previously added mapping, etc.\n\n#### Override a previously defined mapping ####\nFirst install a handler on `node` that invokes `charTyped(String character)` for each typed character. Later override the \u003ckbd\u003eTab\u003c/kbd\u003e character with `tabTyped()`. All other characters should still be handled by `charTyped(character)`.\n\n```java\nNodes.addInputMap(node, consume(keyTyped(), e -\u003e charTyped(e.getCharacter())));\n\n// later override the Tab character\nNodes.addInputMap(node, consume(keyTyped(\"\\t\"), e -\u003e tabTyped()));\n```\n\n#### Override even a more specific previous mapping ####\nThe `Control` might have installed a \u003ckbd\u003eTab\u003c/kbd\u003e-pressed handler for \u003ckbd\u003eTab\u003c/kbd\u003e navigation, but you want to consume all letter, digit and whitespace keys (maybe because you are handling their corresponding key-typed events). The point here is that the previously installed \u003ckbd\u003eTab\u003c/kbd\u003e handler is overridden even if it is more specific than the letter/digit/whitespace handler.\n\n```java\nNodes.addInputMap(node, consume(keyPressed(TAB), e -\u003e tabNavigation()));\n\n// later consume all letters, digits and whitespace\nNodes.addInputMap(node, consume(keyPressed(kc -\u003e kc.isLetterKey() || kc.isDigitKey() || kc.isWhitespaceKey())));\n```\n\n#### Add default mappings ####\nIt has to be possible to add default (or fallback) mappings, i.e. mappings that do not override any previously defined mappings, but take effect if the event is not handled by any previously installed mapping. That is the case for mappings added by skins, since skin is only installed after the user has instantiated the control and customized the mappings.\n\nThe task is to _first_ install the (custom) \u003ckbd\u003eTab\u003c/kbd\u003e handler (`tabTyped()`) and _then_ the (default) key typed handler (`charTyped(c)`), but the custom handler should not be overridden by the default handler.\n\n```java\n// user-specified Tab handler\nNodes.addInputMap(node, consume(keyTyped(\"\\t\"), e -\u003e tabTyped()));\n\n// later in skin\nNodes.addFallbackInputMap(node, consume(keyTyped(), e -\u003e charTyped(e.getCharacter())));\n```\n\n#### Ignore certain events ####\nSuppose the skin defines a generic key-pressed handler, but the user needs \u003ckbd\u003eTab\u003c/kbd\u003e-pressed to be ignored by the control and bubble up the scene graph.\n\n```java\n// ignore Tab handler\nNodes.addInputMap(node, ignore(keyPressed(TAB)));\n\n// later in skin\nNodes.addFallbackInputMap(node, consume(keyPressed(), e -\u003e handleKeyPressed(e)));\n```\n\n#### Remove a previously added handler ####\nWhen changing skins, the skin that is being disposed should remove any mappings it has added to the control. Any mappings added before or after the skin was instantiated should stay in effect. In this example, let's add handlers for each of the arrow keys and for mouse move with left button pressed. Later, remove all of them, but leaving any other mappings untouched.\n\n```java\n// on skin creation\nInputMap\u003cInputEvent\u003e im = sequence(\n        consume(keyPressed(UP),    e -\u003e moveUp()),\n        consume(keyPressed(DOWN),  e -\u003e moveDown()),\n        consume(keyPressed(LEFT),  e -\u003e moveLeft()),\n        consume(keyPressed(RIGHT), e -\u003e moveRight()),\n        consume(\n                mouseMoved().onlyIf(MouseEvent::isPrimaryButtonDown),\n                e -\u003e move(e.getX(), e.getY())));\nNodes.addFallbackInputMap(node, im);\n\n// on skin disposal\nNodes.removeInputMap(node, im);\n```\n\n#### Common post-consumption processing ####\nSuppose we have a number of input mappings whose handlers share some common at the end. We would like to factor out this common code to avoid repetition. To give an example, suppose each `move*()` method from the previous example ends with `this.moveCount += 1`. Let's factor out this common code to a single place. (Notice the `ifConsumed`.)\n\n```java\nInputMap\u003cInputEvent\u003e im0 = sequence(\n        consume(keyPressed(UP),    e -\u003e moveUp()),\n        consume(keyPressed(DOWN),  e -\u003e moveDown()),\n        consume(keyPressed(LEFT),  e -\u003e moveLeft()),\n        consume(keyPressed(RIGHT), e -\u003e moveRight()),\n        consume(\n                mouseMoved().onlyIf(MouseEvent::isPrimaryButtonDown),\n                e -\u003e move(e.getX(), e.getY()))\n        ).ifConsumed(e - { this.moveCount += 1; });\n\nNodes.addFallbackInputMap(node, im);\n```\n\n#### Temporary installation of an InputMap ####\nSuppose one wants to use a given `InputMap` for a node's basic behavior, and upon a specific trigger (e.g. the user presses CTRL+Space), we want the node to have a different behavior temporarily. Once another trigger occurs in this \"special behavior\" context (e.g. the user presses ESC), we want to revert back to the basic behavior. How can this be done?\n\n````java\n// Basic idea\nInputMap\u003c?\u003e anInputMap = // creation code\nInputMap\u003c?\u003e aTempInputMap = // creation code\n\n// install anInputMap\nNodes.addInputMap(node, anInputMap);\n// uninstall anInputMap and install aTempInputMap\nNodes.pushInputMap(node, aTempInputMap);\n// uninstall aTempInputMap and reinstall anInputMap\nNodes.popInputMap(node);\n````\n\nFor example:\n\n````java\n// Special Behavior: refuse to show user a message\nInputMap\u003cEvent\u003e specialBehavior = sequence(\n    // individual input maps here\n    consume(\n            keyPressed(\"a\"),\n            e -\u003e System.out.println(\"We aren't showing you what the user pressed :-p\"),\n\n    // handler for reverting back to basic behavior\n    consume(\n        // trigger that will reinstall basic behavior\n        keyPressed(ESC),\n\n        // uninstalls this behavior from this node and reinstalls the basic behavior\n        e -\u003e {\n            boolean basicBehaviorReinstalled = Nodes.popInputMap(this);\n            if (!basicBehaviorReinstalled) {\n                throw new IllegalStateException(\"Basic behavior was not reinstalled!\");\n            }\n    })\n);\n// Basic Behavior: show user a message\nInputMap\u003cEvent\u003e basicBehavior = sequence(\n    // individual input maps here\n    consume(\n        keyPressed(\"a\"),\n        e -\u003e System.out.println(\"The user pressed: \" + e.getText()),\n\n    // handler for installing special behavior temporarily\n    consume(\n        // trigger that will install new behavior\n        keyPressed(SPACE, CONTROL),\n\n        e -\u003e Nodes.pushInputMap(this, specialBehavior)\n    )\n);\nNodes.addInputMap(node, basicBehavior);\n\n// user presses 'A'\n// System outputs: \"The user pressed: A\"\n\n// user presses CTRL + Space\n// user presses 'A'\n// System outputs: \"We aren't showing you what the user pressed :-p\"\n\n// user presses 'ESC'\n// user presses 'A'\n// System outputs: \"The user pressed: A\"\n````\n\nThese temporary `InputMap`s can be stacked multiple times, so that one can have multiple contexts:\n- basic context\n  - Up Trigger: when user presses `CTRL+SPACE`, uninstalls this context's behavior and installs `temp context 1`\n- temp context 1\n  - Down Trigger: when user presses `ESC`, uninstalls this context's behavior and reinstalls `basic context`\n  - Up Trigger: when user presses `CTRL+SPACE`, uninstalls this context's behavior and installs `temp context 2`\n- temp context 2\n  - Down Trigger: when user presses `ESC`, uninstalls this context's behavior and reinstalls `temp context 1`\n\n### Structural sharing between input maps ###\nConsider a control that defines _m_ input mappings and that there are _n_ instances of this control in the scene. The space complexity of all input mappings of all these controls combined is then _O(n*m)_. The goal is to reduce this complexity to _O(m+n)_ by having a shared structure of complexity _O(m)_ of the _m_ input mappings, and each of the _n_ controls to have an input map that is a constant overhead (_O(1)_) on top of this shared structure.\n\nThis is supported by package `org.fxmisc.wellbehaved.event.template`.\nThe shared structure is an instance of `InputMapTemplate`.\nThe API for constructing `InputMapTemplate`s very much copies the API for constructing `InputMap`s that you have seen throughout this document, except the handlers take an additional argument\u0026mdash;typically the control or the \"behavior\". A template can then be _instantiated_ to an `InputMap`, which is a constant overhead wrapper around the template, by providing the control/behavior object.\n\n**Example:**\n\n```java\nstatic final InputMapTemplate\u003cTextArea, InputEvent\u003e INPUT_MAP_TEMPLATE =\n    unless(TextArea::isDisabled, sequence(\n        consume(keyPressed(A, SHORTCUT_DOWN), (area, evt) -\u003e area.selectAll()),\n        consume(keyPressed(C, SHORTCUT_DOWN), (area, evt) -\u003e area.copy())\n        /* ... */\n    ));\n\nTextArea area1 = new TextArea();\nTextArea area2 = new TextArea();\n\nInputMapTemplate.installFallback(INPUT_MAP_TEMPLATE, area1);\nInputMapTemplate.installFallback(INPUT_MAP_TEMPLATE, area2);\n```\n\nNotice that `INPUT_MAP_TEMPLATE` is `static` and then added to two `TextArea`s.\n\nDownload\n--------\n\nMaven artifacts are deployed to Maven Central repository with the following Maven coordinates:\n\n| Group ID               | Artifact ID    | Version |\n| :--------------------: | :------------: | :-----: |\n| org.fxmisc.wellbehaved | wellbehavedfx  | 0.3.3     |\n\n### Gradle example\n\n```groovy\ndependencies {\n    compile group: 'org.fxmisc.wellbehaved', name: 'wellbehavedfx', version: '0.3.3'\n}\n```\n\n### Sbt example\n\n```scala\nlibraryDependencies += \"org.fxmisc.wellbehaved\" % \"wellbehavedfx\" % \"0.3.3\"\n```\n\n### Manual download\n\n[Download](https://oss.sonatype.org/content/groups/public/org/fxmisc/wellbehaved/wellbehavedfx/0.3.3/) the JAR file and place it on your classpath.\n\n\nLinks\n-------\n\nLicense: [BSD 2-Clause License](http://opensource.org/licenses/BSD-2-Clause)\nAPI documentation: [Javadoc](http://fxmisc.github.io/wellbehaved/javadoc/0.3.3/overview-summary.html)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffxmisc%2Fwellbehavedfx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffxmisc%2Fwellbehavedfx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffxmisc%2Fwellbehavedfx/lists"}