---
title: Context Actions
---

<Callout>
Added in Fzzy Config 0.6.0
</Callout>

There is a robust Context Handling system built into Fzzy Config. This system:
* Captures key inputs with a custom keybind-like system
  * For an actual keybind implementation, see [Keybinds](../config-concepts/validation/Keybinds)
* Acts on inputs across "layered" context handling systems
* Opens actions in a right click menu where and when applicable

## The Foundation
The Context Action system, at its core, is a layered user-input handling technique. It was birthed from and inspired by techniques built into Minecraft itself:
* Keybinds
* Screen key and mouse input 
* Narration building

The vanilla keybind and screen input handling systems both have flaws that this context action system aims to address.
* Keybinds in vanilla overwrite each other if you double-map a keybind to the same key
* Keybinds can't have modifiers (Ctrl, Shift, Alt)
* To capture inputs on screens, you need to hardcode each interaction, properly ensuring execution order and so on. 
* Minecraft in general has no concept of context menus (right click menus)

<Callout variant="info">
Screens that use this system must extend from <code>PopupWidgetScreen</code> to utilize context menus (keybind handling can be done anywhere).
</Callout>

### The `ContextType`
The first step in resolving these and other issues was unifying the input capture method. Why should screens not use keybinds just like... keybinds?

Enter `ContextType`. A ContextType is two things (unless it isn't, see below), an input listener and a `Map` key. The first is pretty self-explanatory. It accepts calls from a parent system and listens for relevant inputs. As a Map key, ContextType is used when building context menus for organization and ordering.

Any system can interact with ContextTypes in a unified manner. Be it keybind inputs from the keyboard, input codes from screen methods, or otherwise. Either mouse or keyboard input can be handled.

#### Example

<CodeTabs>

```java !!tabs Java
//FC comes with a range of pre-defined common context types
ContextType.SAVE
ContextType.FIND
ContextType.COPY
ContextType.CONTEXT_KEYBOARD
//and so on. These can be used in handling without re-defining them. You should only redefine a type if you need the same input structure for a completely different user action result.

//new context types can be defined with the create method
ContextType NEW = ContextType.create("new_file", ContextInput.KEYBOARD,  (int inputCode, boolean ctrl, boolean shift, boolean alt) -> {
    inputCode == GLFW.GLFW_KEY_N && ctrl && !shift && !alt
});
```

```kotlin !!tabs Kotlin
//FC comes with a range of pre-defined common context types
ContextType.SAVE
ContextType.FIND
ContextType.COPY
ContextType.CONTEXT_KEYBOARD
//and so on. These can be used in handling without re-defining them. You should only redefine a type if you need the same input structure for a completely different user action result.

//new context types can be defined with the create method
val NEW = ContextType.create("new_file", ContextInput.KEYBOARD) { inputCode: Int, ctrl: Boolean, shift: Boolean, alt: Boolean ->
    inputCode == GLFW.GLFW_KEY_N && ctrl && !shift && !alt
}
```

</CodeTabs>

### Now What?
Now that we have a universal input-capture system, what do we do with it? We handle the input of course! But before we get into that, we have to talk about layers *pause for Shrek onion joke*. The context type system can detect more than one relevant input for a given user input scenario. If the user pressed `Ctrl + Shift + Z`, we might have a `Ctrl + Shift + Z` and plain `Z` type returning as relevant. This is handled with a layered, event-like approach that passes information back and forth between layers

| Layer         | Purpose                                       | Direction     |
|---------------|-----------------------------------------------|---------------|
| Game Engine   | Handling in-game keybinds                     | Downstream ⬇️ |
| Screen        | Screen-wide context                           |               |
| List          | List actions, page up/down etc.               |               |
| List Element  | Individual element actions, clear, copy, etc. |               |
| Element Child | specialized actions                           | Upstream ⬆️   |

### The `ContextHandler`
The `ContextHandler` interface is designed to do just that, handle a passed context type. Any piece of your interaction puzzle that needs to manage inputs should implement this interface. 

Handling a received context input starts at the "head" of the applicable game layers and proceeds **downstream**. For a screen input, that would be at the screen layer. If the screen has a valid need for the passed type, it should handle it and pass back execution there. If it doesn't, pass handling to its child(ren). Rinse and repeat.

You end up with a downstream cascade, passing handling down as needed or returning back on success

| Layer         | Not Handled             | Handled        |
|---------------|-------------------------|----------------|
| Screen        | Pass to List ⬇️         | Return true ↗️ |
| List          | Pass to List Element ⬇️ | Return true ↗️ |
| List Element  | Pass to Child ⬇️        | Return true ↗️ |
| Element Child | Return false ↗️         | Return true ↗️ |

#### Example

<CodeTabs>

```java !!tabs Java
public class Parent implements ContextHandler {

    public Parent() {}

    private Child child = new Child();

    //this handling prioritizes the parents actions over the childs, it can be refactored to care about the child first of course
    @Override
    public boolean handleContext(ContextType contextType, Position position) {
        if (contextType == ContextType.A) {
            return handleContextA(position); //some parent-wide action
        } else if (contextType == ContextType.B) {
            return handleContextB(position); //another parent-wide action
        } else {
            return child.handleContext(contextType, position); //no applicable parent-wide handling, we move to the child
        }
    }
}

class Child implements ContextHandler {

    //child handles whatever it needs, and then since it's the furthest downstream layer, returns false if it can't handle the input, per the flow diagram above.
    @Override
    public boolean handleContext(ContextType contextType, Position position) {

        //we can scope in the passed position to be relevant to this childs working position
        Position newPosition = position.copy(position.contextInput, position.mX, position.mY, this.x, this.y, this.width, this.height, position.screenWidth, position.screenHeight);

        //handle relevant context types, otherwise terminate the chain with failure since this is the last layer
        if (contextType == ContextType.C) {
            return handleContextC(newPosition); //some child-specific action
        } else if (contextType == ContextType.D) {
            return handleContextB(newPosition); //another another child-specific action
        } else {
            return false; // no further downstream layers, so we return a failure from here
        }
    }
}
```

```kotlin !!tabs Kotlin
class Parent: ContextHandler {

    private val child = Child()

    //this handling prioritizes the parents actions over the childs, it can be refactored to care about the child first of course
    override fun handleContext(contextType: ContextType, position: Position): Boolean {
        return when(contextType) {
            ContextType.A -> handleContextA(position) //some parent-wide action
            ContextType.B -> handleContextB(position) //another parent-wide action
            else -> child.handleContext(contextType, position) //no applicable parent-wide handling, we move to the child
        }
    }
}

class Child: ContextHandler {

    //child handles whatever it needs, and then since it's the furthest downstream layer, returns false if it can't handle the input, per the flow diagram above.
    override fun handleContext(contextType: ContextType, position: Position): Boolean {

        //we can scope in the passed position to be relevant to this childs working position
        val newPosition = position.copy(x = this.x, y = this.y, width = this.width, height = this.height)

        //handle relevant context types, otherwise terminate the chain with failure since this is the last layer
        return when(contextType) {
            ContextType.C -> handleContextC(newPosition) //some child-specific action
            ContextType.D -> handleContextD(newPosition) //another another child-specific action
            else -> false // no further downstream layers, so we return a failure from here
        }
    }
}
```

</CodeTabs>

### The Context Action
The class that names the whole system, `ContextAction`. Context Actions are not strictly necessary in the grand scheme of a Context Action system, but provide two key benefits for use:
* Provide a structured means of creating context callbacks for later use in a context handler. If you want to see an example of this in FC, you can explore how `ConfigEntry` handles context.
* Are used in context menus (right click menus), particularly with the `ContextProvider` framework (see below).

Outside of these two circumstances, it is generally valid to handle context "manually" inline with where the context handler is.

`ValidatedField` is the best example of building context handling using actions, paired with 'ConfigEntry' as the final consumer of the building.

<CodeTabs>

```java !!tabs Java
//ValidatedField has a method contextActionBuilder which is used to build a context map. You can see the default implementation in ValidatedField itself to get a gist of the process, and in ValidatedList to see an example of layering actions.
@Override
public Map<String, Map<ContextType, ContextAction.Builder>> contextActionBuilder(EntryCreator.CreatorContext context) {
    Map<ContextType, ContextAction.Builder> map = new LinkedHashMap(); //linked is important here; maintains the visual ordering of actions if used in a menu
    ContextAction.Builder action = ContextAction.Builder("my.action.translation.key".translate(), position -> {
            //code upon action being fired goes here
            return true; }) //return true/false based on handling result
            .icon(MY_CUSTOM_CONTEXT_ICON) //a small icon can be passed here; it will appear in the context menu to the left of the action name.
            .active(() -> /* supplier for when the action can be performed */);

    map.put(MY_CONTEXT_TYPE, action); //add our action into a map using a ContextType as a key
    Map<String, Map<ContextType, ContextAction.Builder>> map2 = super.contextActionBuilder(context); //getting the builder map from the base builder method
    map2.computeIfAbsent(ContextResultBuilder.ENTRY, k -> new LinkedHashMap()).putAll(map); //apply the new action to the previously built ENTRY section, if any.
    return map2;
}
```

```kotlin !!tabs Kotlin
//ValidatedField has a method contextActionBuilder which is used to build a context map. You can see the default implementation in ValidatedField itself to get a gist of the process, and in ValidatedList to see an example of layering actions.
override fun contextActionBuilder(context: EntryCreator.CreatorContext): MutableMap<String, MutableMap<ContextType, ContextAction.Builder>> {
    val map: MutableMap<ContextType, ContextAction.Builder> = mutableMapOf()
    val action = ContextAction.Builder("my.action.translation.key".translate()) {
            //code upon action being fired goes here
            true } //return true/false based on handling result
            .icon(MY_CUSTOM_CONTEXT_ICON) //a small icon can be passed here; it will appear in the context menu to the left of the action name.
            .active { /* supplier for when the action can be performed */ }

    map[MY_CONTEXT_TYPE] = action //add our action into a map using a ContextType as a key
    val map2 = super.contextActionBuilder(context) //getting the builder map from the base builder method
    map2.computeIfAbsent(ContextResultBuilder.ENTRY) { mutableMapOf() }.putAll(map) //apply the new action to the previously built ENTRY section, if any.
    return map2
}
```

</CodeTabs>

### Context Providers
The `ContextProvider` interface helps tie everything discussed above together. While the `ContextHandler` works downstream, ContextProvider works upstream, passing context information back up the chain for consumption by a further upstream handler. This achieves two major goals:
* Providing a mechanism for a context handler to request context actions from children without them needing to themselves be handlers.
* Building a structure for a context menu (right click menu)

Considering the context handler flow diagram above, we can see how a handler and provider could work together:

| Layer         | Downstream                      | Upstream                                                        |
|---------------|---------------------------------|-----------------------------------------------------------------|
| Screen        | Pass to List ⬇️                 | Return true ↗️                                                  |
| List          | **Request context for menu** ⬇️ | Use builder to open context menu<br /><br />Return true ↗️      |
| List Element  | Pass ContextBuilder ⬇️          | Append own actions<br /><br />Builder ready for parent layer ⬆️ |
| Element Child | Add actions to builder ➡️       | Builder ready for parent layer ⬆️                               |

Using the same context handling example from above, we'll add in a provider system

<CodeTabs>

```java !!tabs Java
class Parent implements ContextHandler, ContextProvider {

    public Parent() {}

    private Child child = new Child();

    //this handling prioritizes the parents actions over the childs, it can be refactored to care about the child first of course
    @Override
    public boolean handleContext(contextType: ContextType, position: Position) {
        if (contextType == ContextType.A) {
            return handleContextA(position); //some parent-wide action
        } else if (contextType == ContextType.B) {
            return handleContextB(position); //another parent-wide action
        } else {
            Position position = Position(/* Position information */) //create a position context for this element
            ContextResultProvider contextBuilder = ContextProvider.empty(position); //create an empty context builder to pass downstream
            provideContext(contextBuilder); //build into the empty context
            if (contextType == ContextType.CONTEXT_KEYBOARD || contextType == ContextType.CONTEXT_MOUSE) {
                Popups.INSTANCE.openContextMenuPopup(contextBuilder);
                return true;
            } else {
                //build and flatten the context inputs and act on a provided action if one matching the contextType exists
                //Note that flatBuild only works well if all of the provided contextTypes are unique across groups. If groups might have duplicate keys, use build and handle duplicate "hits" somehow
                Map<ContextType, ContextAction> m = contextBuilder.flatBuild();
                if (!m.containsKey(contextType)) {
                    return false;
                } else {
                    return m.get(contextType).getAction().apply(contextBuilder.position());
                }
            }
        }
    }

    // pass the request downstream first, build onto that last. This puts the most scoped-in contexts at the top of the context menu, and the most generalized actions at the bottom.
    @Override
    public void provideContext(builder: ContextResultBuilder) {
         child.provideContext(builder);
         builder.add("parent_group", PARENT_CONTEXT_TYPE, parentContextActionBuilder);
    }
}

class Child implements ContextProvider {

    // child builds in its actions as needed
    @Override
    public void provideContext(builder: ContextResultBuilder) {
         builder.add("child_group", CHILD_CONTEXT_TYPE_A, builderA);
         builder.add("child_group", CHILD_CONTEXT_TYPE_B, builderB);
    }
}
```

```kotlin !!tabs Kotlin
class Parent: ContextHandler, ContextProvider {

    private val child = Child()

    //this handling prioritizes the parents actions over the childs, it can be refactored to care about the child first of course
    override fun handleContext(contextType: ContextType, position: Position): Boolean {
        return when (contextType) {
            ContextType.A -> handleContextA(position) //some parent-wide action
            ContextType.B -> handleContextB(position) //another parent-wide action
            else -> { //handle other inputs with provided context actions
                val position = Position(/* Position information */) //create a position context for this element
                val contextBuilder = ContextProvider.empty(position) //create an empty context builder to pass downstream
                provideContext(contextBuilder) //build into the empty context
                when (contextType) { //act on the provided input type with the built actions
                    ContextType.CONTEXT_KEYBOARD, ContextType.CONTEXT_MOUSE -> { //context menu input (right click, Shift-F10, menu button)
                        openContextMenuPopup(contextBuilder) //open a context menu
                        true //return true, context handled
                    }
                    else -> { //some other so-far unhandled input
                        //build and flatten the context inputs and act on a provided action if one matching the contextType exists
                        //Note that flatBuild only works well if all of the provided contextTypes are unique across groups. If groups might have duplicate keys, use build and handle duplicate "hits" somehow
                        return contextBuilder.flatBuild()[contextType]?.action?.apply(contextBuilder.position()) ?: false
                    }
                }
            }
        }
    }

    // pass the request downstream first, build onto that last. This puts the most scoped-in contexts at the top of the context menu, and the most generalized actions at the bottom.
    override fun provideContext(builder: ContextResultBuilder) {
         child.provideContext(builder)
         builder.add("parent_group", PARENT_CONTEXT_TYPE, parentContextActionBuilder)
    }
}

class Child: ContextProvider {

    // child builds in its actions as needed
    override fun provideContext(builder: ContextResultBuilder) {
         builder.add("child_group", CHILD_CONTEXT_TYPE_A, builderA)
         builder.add("child_group", CHILD_CONTEXT_TYPE_B, builderB)
    }
}
```

</CodeTabs>