Skip to content

Handling input events

Bartłomiej Dach edited this page Aug 7, 2023 · 12 revisions

Input handling follows a topological model whereby the top-most (visually) Drawable handles input prior to anything else in the game. Hierarchically between a single parent-child relationship, the child handles input before the parent.

Parent              < Handler #4
    Child_1         < Handler #3
    Child_2         < Handler #2
        Child_2_1   < Handler #1

Positional vs non-positional input

"Positional" input refers to any input that depends on a screen-space position (e.g. hover). "Non-positional" input refers to any other input (e.g. a button press).

Individual event handlers

Positional

protected virtual bool OnMouseMove(MouseMoveEvent e);

protected virtual bool OnHover(HoverEvent e);
protected virtual void OnHoverLost(HoverLostEvent e);

protected virtual bool OnMouseDown(MouseDownEvent e);
protected virtual void OnMouseUp(MouseUpEvent e);

protected virtual bool OnClick(ClickEvent e);
protected virtual bool OnDoubleClick(DoubleClickEvent e);

protected virtual bool OnDragStart(DragStartEvent e);
protected virtual void OnDrag(DragEvent e);
protected virtual void OnDragEnd(DragEndEvent e);

protected virtual bool OnScroll(ScrollEvent e);

protected virtual bool OnTouchDown(TouchDownEvent e);
protected virtual void OnTouchMove(TouchMoveEvent e);
protected virtual void OnTouchUp(TouchUpEvent e);

protected virtual bool OnTabletPenButtonPress(TabletPenButtonPressEvent e);
protected virtual void OnTabletPenButtonRelease(TabletPenButtonReleaseEvent e);

Non-positional

protected virtual bool OnKeyDown(KeyDownEvent e);
protected virtual void OnKeyUp(KeyUpEvent e);

protected virtual bool OnJoystickPress(JoystickPressEvent e);
protected virtual void OnJoystickRelease(JoystickReleaseEvent e);
protected virtual bool OnJoystickAxisMove(JoystickAxisMoveEvent e);

protected virtual bool OnMidiDown(MidiDownEvent e);
protected virtual void OnMidiUp(MidiUpEvent e);

protected virtual bool OnTabletAuxiliaryButtonPress(TabletAuxiliaryButtonPressEvent e);
protected virtual void OnTabletAuxiliaryButtonRelease(TabletAuxiliaryButtonReleaseEvent e);

These event handlers should be used when singular events are to be handled.

The boolean return value indicates whether the event should continue being propagated to other Drawables in the scene graph.

Example: If a child did not want its parent to receive an OnHover() event, it should override OnHover() to return true.

OnHoverLost() is an exception which is unconditionally invoked on every un-hovered Drawable.

Example:

protected override bool OnClick(ClickEvent e)
{
    this.ScaleTo(1.25f, 50).Then().ScaleTo(1f, 50);
    return true;
}

Input handlers that correspond to continuations/resolutions of previous input, such as OnMouseUp(), OnDragEnd(), OnKeyUp(), etc., will only fire on drawables that registered to handle the original input by returning true - for the examples above, that would be OnMouseDown(), OnDragStart(), OnKeyDown(), etc. Those events cannot be suppressed.

Aggregate event handler

protected virtual bool Handle(UIEvent e);

This event handler should be used when groups of events with identical implementations are to be handled.

This counts as both a positional and non-positional event handler and works well when combined with the HandleNonPositionalInput and HandlePositionalInput properties described below.

Example

protected override bool Handle(UIEvent e)
{
    switch (e)
    {
        case MouseEvent _:
            // Stop all mouse events from being handled by other Drawables
            return true;

        default:
            // Let other Drawables handle everything else
            return false;
    }
}

Controlling whether input is to be handled

By default, any Drawable that implements the handlers described above will receive all events appropriate for the types of input listed.

public virtual bool HandleNonPositionalInput;
public virtual bool HandlePositionalInput;

The above properties are provided to control whether a Drawable should receive the types of input events.

Example: If a Drawable did not want to handle any click, drag, and hover events at some point, it could achieve this by overriding HandlePositionalInput to return false.

Warning: Overriding the above properties will cause the Drawable to be considered for (but not necessarily receive) that type of input, and thus serves as an anti-optimisation if the intention is to make the Drawable not considered for that type of input.

If a parent should control whether itself or its children should be considered for a type of input at all, the following properties are provided to control the behaviour:

public virtual bool PropagateNonPositionalInputSubTree;
public virtual bool PropagatePositionalInputSubTree;

Example:

class MyContainer : FillFlowContainer
{
    public MyContainer()
    {
        for (int i = 0; i < 1024; i++)
            Add(new Button { Size = new Vector2(50) });
    }

    // Whoah! The buttons we added are just for show, they don't actually handle clicks!
    public override bool PropagatePositionalInputSubTree => false;
}

Receiving and handling focus

public virtual bool RequestsFocus;
public virtual bool AcceptsFocus;

protected virtual void OnFocus(FocusEvent e);
protected virtual void OnFocusLost(FocusLostEvent e);

Focus may be received via both positional and non-positional input. A Drawable with AcceptsFocus = false will never receive focus.

Positional

A Drawable receives focus when OnClick() returns true.

Non-positional

The top-most Drawable with RequestsFocus = true and HandleNonPostionalInput = true will receive focus if nothing else has focus.

The OnFocusLost() method is unconditionally invoked on the un-focused Drawable.

Example:

class MyDrawable : CompositeDrawable
{
    public override bool AcceptsFocus => true;
    protected override bool OnClick(ClickEvent e) => true;

    protected override void OnFocus(FocusEvent e) => this.ScaleTo(1.5f, 50);
    protected override void OnFocusLost(FocusLostEvent e) => this.ScaleTo(1f, 50);
}

Input event hierarchy

The following hierarchical relationship of input events can be useful to know when drilling down with the aggregate event handler.

classDiagram
    direction LR
    UIEvent <|-- FocusEvent
    UIEvent <|-- FocusLostEvent
    UIEvent <|-- JoystickEvent
    JoystickEvent <|-- JoystickAxisMoveEvent
    JoystickEvent <|-- JoystickButtonEvent
    JoystickButtonEvent <|-- JoystickPressEvent
    JoystickButtonEvent <|-- JoystickReleaseEvent
    UIEvent <|-- KeyBindingEvent
    KeyBindingEvent <|-- KeyBindingPressEvent
    KeyBindingEvent <|-- KeyBindingReleaseEvent
    KeyBindingEvent <|-- KeyBindingScrollEvent
    UIEvent <|-- KeyboardEvent
    KeyboardEvent <|-- KeyDownEvent
    KeyboardEvent <|-- KeyUpEvent
    UIEvent <|-- MidiEvent
    MidiEvent <|-- MidiDownEvent
    MidiEvent <|-- MidiUpEvent
    UIEvent <|-- MouseEvent
    MouseEvent <|-- HoverEvent
    MouseEvent <|-- HoverLostEvent
    MouseEvent <|-- MouseButtonEvent
    MouseButtonEvent <|-- ClickEvent
    MouseButtonEvent <|-- DoubleClickEvent
    MouseButtonEvent <|-- DragEndEvent
    MouseButtonEvent <|-- DragEvent
    MouseButtonEvent <|-- DragStartEvent
    MouseButtonEvent <|-- MouseDownEvent
    MouseButtonEvent <|-- MouseUpEvent
    MouseEvent <|-- MouseMoveEvent
    MouseEvent <|-- ScrollEvent
    UIEvent <|-- TabletEvent
    TabletEvent <|-- TabletAuxiliaryButtonEvent
    TabletAuxiliaryButtonEvent <|-- TabletAuxiliaryButtonPressEvent
    TabletAuxiliaryButtonEvent <|-- TabletAuxiliaryButtonReleaseEvent
    TabletEvent <|-- TabletPenButtonEvent
    TabletPenButtonEvent <|-- TabletPenButtonPressEvent
    TabletPenButtonEvent <|-- TabletPenButtonReleaseEvent
    UIEvent <|-- TouchEvent
    TouchEvent <|-- TouchDownEvent
    TouchEvent <|-- TouchMoveEvent
    TouchEvent <|-- TouchUpEvent
Loading
Clone this wiki locally