Composable event handlers and skin scaffolding for JavaFX controls.

Overview

This project is no longer being maintained. See this issue for more details.

WellBehavedFX

This 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 (see also JDK-8076423), but was dropped. (I guess I was the most vocal opponent of the proposal (link to discussion thread).)

Use Cases

Event Matching

Use cases in this section focus on expressivity of event matching, i.e. expressing what events should be handled.

Key Combinations

The task is to add handlers for the following key combinations to Node node:

Key Combination Comment Handler
Enter no modifier keys pressed enterPressed()
[Shift+]A optional Shift, no other modifiers aPressed()
Shortcut+Shift+S saveAll()
Nodes.addInputMap(node, sequence(
    consume(keyPressed(ENTER),                        e -> enterPressed()),
    consume(keyPressed(A, SHIFT_ANY),                 e -> aPressed()),
    consume(keyPressed(S, SHORTCUT_DOWN, SHIFT_DOWN), e -> saveAll())
));

Same action for different events

In 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 Space-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).

Nodes.addInputMap(button, consume(
        anyOf(mouseClicked(PRIMARY), keyPressed(SPACE)),
        e -> action()));

Text Input

Handle 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.

Nodes.addInputMap(button, consume(
        keyTyped(c -> !c.equals("\n")),
        e -> System.out.print(e.getCharacter())));

Custom Events

Assume the following custom event declaration:

class FooEvent extends Event {
    public static final EventType<FooEvent> FOO;

    public boolean isSecret();
    public String getValue();
}

The 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.

Nodes.addInputMap(node, consume(
        eventType(FooEvent.FOO).unless(FooEvent::isSecret),
        e -> System.out.print(e.getValue())));

Manipulating Input Mappings

Use 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.

Override a previously defined mapping

First install a handler on node that invokes charTyped(String character) for each typed character. Later override the Tab character with tabTyped(). All other characters should still be handled by charTyped(character).

Nodes.addInputMap(node, consume(keyTyped(), e -> charTyped(e.getCharacter())));

// later override the Tab character
Nodes.addInputMap(node, consume(keyTyped("\t"), e -> tabTyped()));

Override even a more specific previous mapping

The Control might have installed a Tab-pressed handler for Tab 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 Tab handler is overridden even if it is more specific than the letter/digit/whitespace handler.

Nodes.addInputMap(node, consume(keyPressed(TAB), e -> tabNavigation()));

// later consume all letters, digits and whitespace
Nodes.addInputMap(node, consume(keyPressed(kc -> kc.isLetterKey() || kc.isDigitKey() || kc.isWhitespaceKey())));

Add default mappings

It 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.

The task is to first install the (custom) Tab handler (tabTyped()) and then the (default) key typed handler (charTyped(c)), but the custom handler should not be overridden by the default handler.

// user-specified Tab handler
Nodes.addInputMap(node, consume(keyTyped("\t"), e -> tabTyped()));

// later in skin
Nodes.addFallbackInputMap(node, consume(keyTyped(), e -> charTyped(e.getCharacter())));

Ignore certain events

Suppose the skin defines a generic key-pressed handler, but the user needs Tab-pressed to be ignored by the control and bubble up the scene graph.

// ignore Tab handler
Nodes.addInputMap(node, ignore(keyPressed(TAB)));

// later in skin
Nodes.addFallbackInputMap(node, consume(keyPressed(), e -> handleKeyPressed(e)));

Remove a previously added handler

When 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.

// on skin creation
InputMap<InputEvent> im = sequence(
        consume(keyPressed(UP),    e -> moveUp()),
        consume(keyPressed(DOWN),  e -> moveDown()),
        consume(keyPressed(LEFT),  e -> moveLeft()),
        consume(keyPressed(RIGHT), e -> moveRight()),
        consume(
                mouseMoved().onlyIf(MouseEvent::isPrimaryButtonDown),
                e -> move(e.getX(), e.getY())));
Nodes.addFallbackInputMap(node, im);

// on skin disposal
Nodes.removeInputMap(node, im);

Common post-consumption processing

Suppose 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.)

InputMap<InputEvent> im0 = sequence(
        consume(keyPressed(UP),    e -> moveUp()),
        consume(keyPressed(DOWN),  e -> moveDown()),
        consume(keyPressed(LEFT),  e -> moveLeft()),
        consume(keyPressed(RIGHT), e -> moveRight()),
        consume(
                mouseMoved().onlyIf(MouseEvent::isPrimaryButtonDown),
                e -> move(e.getX(), e.getY()))
        ).ifConsumed(e - { this.moveCount += 1; });

Nodes.addFallbackInputMap(node, im);

Temporary installation of an InputMap

Suppose 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?

// Basic idea
InputMap<?> anInputMap = // creation code
InputMap<?> aTempInputMap = // creation code

// install anInputMap
Nodes.addInputMap(node, anInputMap);
// uninstall anInputMap and install aTempInputMap
Nodes.pushInputMap(node, aTempInputMap);
// uninstall aTempInputMap and reinstall anInputMap
Nodes.popInputMap(node);

For example:

// Special Behavior: refuse to show user a message
InputMap<Event> specialBehavior = sequence(
    // individual input maps here
    consume(
            keyPressed("a"),
            e -> System.out.println("We aren't showing you what the user pressed :-p"),

    // handler for reverting back to basic behavior
    consume(
        // trigger that will reinstall basic behavior
        keyPressed(ESC),

        // uninstalls this behavior from this node and reinstalls the basic behavior
        e -> {
            boolean basicBehaviorReinstalled = Nodes.popInputMap(this);
            if (!basicBehaviorReinstalled) {
                throw new IllegalStateException("Basic behavior was not reinstalled!");
            }
    })
);
// Basic Behavior: show user a message
InputMap<Event> basicBehavior = sequence(
    // individual input maps here
    consume(
        keyPressed("a"),
        e -> System.out.println("The user pressed: " + e.getText()),

    // handler for installing special behavior temporarily
    consume(
        // trigger that will install new behavior
        keyPressed(SPACE, CONTROL),

        e -> Nodes.pushInputMap(this, specialBehavior)
    )
);
Nodes.addInputMap(node, basicBehavior);

// user presses 'A'
// System outputs: "The user pressed: A"

// user presses CTRL + Space
// user presses 'A'
// System outputs: "We aren't showing you what the user pressed :-p"

// user presses 'ESC'
// user presses 'A'
// System outputs: "The user pressed: A"

These temporary InputMaps can be stacked multiple times, so that one can have multiple contexts:

  • basic context
    • Up Trigger: when user presses CTRL+SPACE, uninstalls this context's behavior and installs temp context 1
  • temp context 1
    • Down Trigger: when user presses ESC, uninstalls this context's behavior and reinstalls basic context
    • Up Trigger: when user presses CTRL+SPACE, uninstalls this context's behavior and installs temp context 2
  • temp context 2
    • Down Trigger: when user presses ESC, uninstalls this context's behavior and reinstalls temp context 1

Structural sharing between input maps

Consider 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.

This is supported by package org.fxmisc.wellbehaved.event.template. The shared structure is an instance of InputMapTemplate. The API for constructing InputMapTemplates very much copies the API for constructing InputMaps that you have seen throughout this document, except the handlers take an additional argument—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.

Example:

static final InputMapTemplate<TextArea, InputEvent> INPUT_MAP_TEMPLATE =
    unless(TextArea::isDisabled, sequence(
        consume(keyPressed(A, SHORTCUT_DOWN), (area, evt) -> area.selectAll()),
        consume(keyPressed(C, SHORTCUT_DOWN), (area, evt) -> area.copy())
        /* ... */
    ));

TextArea area1 = new TextArea();
TextArea area2 = new TextArea();

InputMapTemplate.installFallback(INPUT_MAP_TEMPLATE, area1);
InputMapTemplate.installFallback(INPUT_MAP_TEMPLATE, area2);

Notice that INPUT_MAP_TEMPLATE is static and then added to two TextAreas.

Download

Maven artifacts are deployed to Maven Central repository with the following Maven coordinates:

Group ID Artifact ID Version
org.fxmisc.wellbehaved wellbehavedfx 0.3.3

Gradle example

dependencies {
    compile group: 'org.fxmisc.wellbehaved', name: 'wellbehavedfx', version: '0.3.3'
}

Sbt example

libraryDependencies += "org.fxmisc.wellbehaved" % "wellbehavedfx" % "0.3.3"

Manual download

Download the JAR file and place it on your classpath.

Links

License: BSD 2-Clause License API documentation: Javadoc

Comments
  • Feature request: Input map stack

    Feature request: Input map stack

    The following code works as expected, but is coupled to the inner-workings of Nodes.java:

      /**
       * This method adds listeners to editor events that can be removed without
       * affecting the original listeners (i.e., the original lister is restored
       * on a call to removeEventListener).
       *
       * @param map The map of methods to events.
       */
      public void addEventListener( final InputMap<InputEvent> map ) {
        nodeMap = (InputMap<InputEvent>)getEditor().getProperties().get( "org.fxmisc.wellbehaved.event.inputmap" );
        Nodes.addInputMap( getEditor(), map );
      }
    
      /**
       * This method removes listeners to editor events and restores the default
       * handler.
       *
       * @param map The map of methods to events.
       */
      public void removeEventListener( final InputMap<InputEvent> map ) {
        Nodes.removeInputMap( getEditor(), map );
        Nodes.addInputMap( getEditor(), nodeMap );
      }
    

    An API for pushing and popping input maps would be helpful:

      public void addEventListener( final InputMap<InputEvent> map ) {
        Nodes.pushInputMap( getEditor(), map );
      }
    
      public void removeEventListener( final InputMap<InputEvent> map ) {
        Nodes.popInputMap( getEditor(), map );
      }
    

    In this situation, a StyleClassedTextArea already has an input map and a number of key bindings. My code intercepts all keyTyped() and keyPressed() events until Esc is pressed, to offer the user an inline autocomplete-like mode. When the user presses Esc, the original input map for the editor should be restored. My code shouldn't need to maintain a reference to the editor's input map.

    opened by DaveJarvis 13
  • org.fxmisc.wellbehaved.event.EventPattern.keyTyped with parameters depends on keyboard layout

    org.fxmisc.wellbehaved.event.EventPattern.keyTyped with parameters depends on keyboard layout

    Hi,

    when I use standard JavaFX handler I can simply check the character of the key event independently of the keyboard layout. I ignore modifiers.

           area.setOnKeyTyped(e -> {
               if (e.getCharacter().equals("}")) {
                   System.out.println(e);
               }
           });
    

    This is not possible with the EventPattern because KeyTypedCombination matches the modifiers with the event. The code below will not print because the "}" is typed Shift + } thus the event has the Shift modifier. consume(keyTyped("}"), e -> System.out.println(e))

    It is necessary to use the Shift modifier which is unintuitive because I am only interested in the character independently of the keyboard layout. consume(keyTyped("}", SHIFT_DOWN), e -> System.out.println(e))

    I think that if a caller puts no modifiers then the event modifiers should be ignored.

    enhancement 
    opened by appsofteng 5
  • New Release for experimental package

    New Release for experimental package

    Is there any additional work you need to do for the experimental InputMap API? Or can a new release be made that includes it so we can integrate it into RichTextFX?

    opened by JordanMartinez 3
  • map and listBind and weak references.

    map and listBind and weak references.

    I want to bind an ObservableList<T> to and `ObservableList.

    I wrote a mapping (map) from the first list to an intermediate one, then i use listBind between the intermediate and the target. The problem is that if i don't keep a strong reference on this intermediate list, it is garbaged and the binding is dispose.

    I wrote this simple test that fails:

    public class ListMapTest {
        @Test
        public void test() throws InterruptedException {
            ObservableList<String> source = FXCollections.observableArrayList();
            source.addAll("1", "2", "8");
    
            ObservableList<Integer> intermediate = EasyBind.map(source, Integer::parseInt);
    
            ObservableList<Integer> target = FXCollections.observableArrayList();
            EasyBind.listBind(target, intermediate);
    
            assertEquals(Arrays.asList(1, 2, 8) , target);
    
            intermediate = null;
            System.gc();
            Thread.sleep(1000);
    
            source.addAll(2, Arrays.asList("3", "5"));
            assertEquals(Arrays.asList(1, 2, 3, 5, 8), target);
    
            source.remove(1, 3);
            assertEquals(Arrays.asList(1, 5, 8), target);
        }
    }
    
    opened by gontard 1
  • No dependency on JavaFX

    No dependency on JavaFX

    Maybe more of a question than an issue.

    I'm working on something that analyzes dependencies recursively through the Gradle API. This library comes up as having no dependnecies which is also reflected the pom. This library should obviously point to JavaFX, though. But after looking at the build.gradle here, I can't find any reference to javafx. The source code here imports javafx packages, so how exactly does this code build without an explicit dependency on javafx? How does it work?

    opened by mgroth0 0
Releases(v0.3.3)
Owner
null
💠 Undecorated JavaFX Scene with implemented move, resize, minimise, maximise, close and Windows Aero Snap controls.

Support me joining PI Network app with invitation code AlexKent FX-BorderlessScene ( Library ) ?? Undecorated JavaFX Scene with implemented move, resi

Alexander Kentros 125 Jan 4, 2023
A collection of JavaFX controls and utilities.

GemsFX At least JDK 11 is required. Dialog Pane The class DialogPane can be used as a layer on top of any application. It offers various methods to di

DLSC Software & Consulting GmbH 269 Jan 5, 2023
Controls for adding Parallax effects for Java (JavaFX)

FXParallax Parallax framework for Java (JavaFX). This framework adds controls to add Parallax effects to JavaFX application, this effect can add a sen

Pedro Duque Vieira 36 Sep 30, 2022
A project that shows the different ways on how to create custom controls in JavaFX

JavaFX Custom Controls This project will show different ways on how to create custom controls in JavaFX. It will cover the following approaches: Resty

Gerrit Grunwald 27 Sep 5, 2022
A collection of Apple UI controls implemented in JavaFX.

Apple FX A collection of Apple UI controls implemented in JavaFX. Available Macos controls: MacosButton MacosCheckBox MacosRadioButton MacosLabel Maco

Gerrit Grunwald 17 Sep 25, 2022
Renders the player skin layer in 3d

3d Skin Layers Replaces the usually flat second layer of player skins with a 3d modeled version. Will automatically switch to the vanilla 2d rendering

tr7zw 95 Jan 8, 2023
Reactive event streams, observable values and more for JavaFX.

ReactFX ReactFX is an exploration of (functional) reactive programming techniques for JavaFX. These techniques usually result in more concise code, le

Tomas Mikula 360 Dec 28, 2022
Reactive JavaFX Event Handling

ReactorFX This lightweight convenience library allows for simple integration between Project Reactor and JavaFX. ReactorFX provides fluent factories t

Jake 33 Dec 30, 2022
Lib-Tile is a multi Maven project written in JavaFX and NetBeans IDE 8 and provides the functionalities to use and handle easily Tiles in your JavaFX application.

Lib-Tile Intention Lib-Tile is a multi Maven project written in JavaFX and NetBeans IDE and provides the functionalities to use and handle easily Tile

Peter Rogge 13 Apr 13, 2022
DataFX - is a JavaFX frameworks that provides additional features to create MVC based applications in JavaFX by providing routing and a context for CDI.

What you’ve stumbled upon here is a project that intends to make retrieving, massaging, populating, viewing, and editing data in JavaFX UI controls ea

Guigarage 110 Dec 29, 2022
Tray Icon implementation for JavaFX applications. Say goodbye to using AWT's SystemTray icon, instead use a JavaFX Tray Icon.

FXTrayIcon Library intended for use in JavaFX applications that makes adding a System Tray icon easier. The FXTrayIcon class handles all the messy AWT

Dustin Redmond 248 Dec 30, 2022
Event-driven trigger + recording system for FFXIV

Triggevent Fully event driven trigger + overlay system for FFXIV. Makes triggers easier to develop and test. Allows triggers to have custom configurat

null 63 Dec 28, 2022
This app displays the perceived strength of a single earthquake event based on the DYFI indicator.

This app displays the perceived strength of a single earthquake event based on the DYFI indicator. Used in a Udacity course in the Android Basics Nanodegree.

Ezaz Ahammad 1 Jan 23, 2022
A simple JavaFX application to load, save and edit a CSV file and provide a JSON configuration for columns to check the values in the columns.

SmartCSV.fx Description A simple JavaFX application to load, save and edit a CSV file and provide a JSON Table Schema for columns to check the values

Andreas Billmann 74 Oct 24, 2022
Lobby System Template for a multiplayer java game, with chat and other features, using JavaFX and socket TCP (will be extended to UDP).

JavaFX-MultiplayerLobbySystem JavaFX lobby system for multiplayer games with chat, ready toggle and kick buttons, using socket TCP by default. Demo Cr

Michele Righi 7 May 8, 2022
A Java framework for creating sophisticated calendar views (JavaFX 8, 9, 10, and 11)

CalendarFX A Java framework for creating sophisticated calendar views based on JavaFX. A detailed developer manual can be found online: CalendarFX 8 D

DLSC Software & Consulting GmbH 660 Jan 6, 2023
A JavaFX 3D Visualization and Component Library

FXyz3D FXyz3D Core: FXyz3D Client: FXyz3D Importers: A JavaFX 3D Visualization and Component Library How to build The project is managed by gradle. To

null 16 Aug 23, 2020
A library for creating and editing graph-like diagrams in JavaFX.

Graph Editor A library for creating and editing graph-like diagrams in JavaFX. This project is a fork of tesis-dynaware/graph-editor 1.3.1, which is n

Steffen 125 Jan 1, 2023
📊 Exposing charts from Java to JavaFX and the Web!

Exposing charts from Java to JavaFX and to the Web! JavaFX · Charts · Websockets · Jetty · Web JavaFxDataviewer is an open-source data visualization t

jasrodis 57 Oct 26, 2022