1 /// This module contains interfaces for mapping input events to input actions.
2 module fluid.io.action;
3 
4 import optional;
5 
6 import fluid.input;
7 import fluid.future.context;
8 
9 public import fluid.input;
10 
11 @safe:
12 
13 /// I/O interface for mapping input events to input actions.
14 ///
15 /// Input events correspond to direct events from input devices, like keyboard or mouse.
16 /// The job of `ActionIO` is to translate them into more meaningful input actions, which nodes
17 /// can set up listeners for.
18 ///
19 /// `ActionIO` will work on nodes that are its children. That means that any input handling node must be placed
20 /// inside as a child will react to these actions. Similarly, nodes representing input devices, also have to be placed
21 /// as children.
22 interface ActionIO : IO {
23 
24     /// Basic input actions necessary for input actions to work.
25     @InputAction
26     enum CoreAction {
27 
28         /// This input action is fired in response to the `frame` input event.
29         frame,
30 
31     }
32 
33     enum Event {
34         noopEvent,
35         frameEvent,
36     }
37 
38     /// Create an input event which should never activate any input action. For propagation
39     /// purposes, this event always counts as handled.
40     ///
41     /// The usual purpose of this event is to prevent input actions from running, assuming
42     /// the `ActionIO` system's logic stops once an event is handled. For example,
43     /// `fluid.io.hover.HoverPointerAction` emits this event when it is ordered to run an input
44     /// action, effectively overriding `ActionIO`'s response.
45     ///
46     /// See_Also:
47     ///     `frameEvent`
48     /// Params:
49     ///     isActive = Should the input event be marked as active or not. Defaults to true.
50     /// Returns:
51     ///     An instance of `Event.noopEvent`.
52     static InputEvent noopEvent(bool isActive = true) {
53 
54         const code = InputEventCode(ioID!ActionIO, Event.noopEvent);
55 
56         return InputEvent(code, isActive);
57 
58     }
59 
60     /// Create an input event to which `ActionIO` should always bind to the `CoreAction.frame` input action.
61     /// Consequently, `ActionIO` always responds with a `CoreAction.frame` input action after processing remaining
62     /// input actions. This can be cancelled by emitting a `noopEvent` before the `frameEvent` is handled.
63     ///
64     /// This can be used by device and input handling I/Os to detect the moment after which all input actions have
65     /// been processed. This means that it can be used to develop fallback mechanisms like `hoverImpl`
66     /// and `focusImpl`, which only trigger if no input action has been activated.
67     ///
68     /// Note that `CoreAction.frame` might, or might not, be emitted if another action event has been emitted during
69     /// the same frame. `InputMapChain` will only emit `CoreAction.frame` is no other input action has been handled.
70     ///
71     /// See_Also:
72     ///     `noopEvent`
73     /// Returns:
74     ///     An instance of `Event.frameEvent`.
75     static InputEvent frameEvent() {
76 
77         const code = InputEventCode(ioID!ActionIO, Event.frameEvent);
78         const isActive = false;
79 
80         return InputEvent(code, isActive);
81 
82     }
83 
84     /// Pass an input event to transform into an input map.
85     ///
86     /// The `ActionIO` system should withhold all input actions until after its node is drawn. This is when
87     /// all input handling nodes that the system interacts with, like `HoverIO` and `FocusIO`, have been processed
88     /// and are ready to handle the event.
89     ///
90     /// Once processing has completed, if the event has triggered an action, the system will trigger the callback that
91     /// was passed along with the event. Events that were saved in the system should be discarded.
92     ///
93     /// Note if an event functions as a modifier — for example the "control" key in a "ctrl+c" action — it should not
94     /// trigger the callback. In such case, only the last key, the "C" key in the example, will perform the call.
95     /// This is to make sure the event is handled by the correct handler, and only once.
96     ///
97     /// Params:
98     ///     event    = Input event the system should save.
99     ///     number   = A number that will be passed as-is into the callback. Can be used to distinguish between
100     ///         different action calls without allocating a closure.
101     ///     callback = Function to call if the event has triggered an input action.
102     ///         The ID of the action will be passed as an argument, along with a boolean indicating if it was
103     ///         triggered by an inactive, or active event.
104     ///         The number passed into the `emitEvent` function will be passed as the third argument to this callback.
105     ///         The return value of the callback should indicate if the action was handled or not.
106     void emitEvent(InputEvent event, int number,
107         bool delegate(immutable InputActionID, bool isActive, int number) @safe callback);
108 
109 }
110 
111 /// Uniquely codes a pressed key, button or a gesture, by using an I/O ID and event code map.
112 /// Each I/O interface can define its own keys and buttons it needs to map. The way it maps
113 /// codes to buttons is left up to the interface to define, but it usually is with an enum.
114 struct InputEventCode {
115 
116     /// ID for the I/O interface representing the input device. The I/O interface defines a code
117     /// for each event it may send. This means the I/O ID along with the event code should uniquely identify events.
118     ///
119     /// An I/O system can create and emit events that belong to another system in order to simulate events
120     /// from another device, however this scenario is likely better handled as a separate binding in `ActionIO`.
121     IOID ioID;
122 
123     /// Event code identifying the key or button that triggered the event. These codes are defined
124     /// by the I/O interface that send them.
125     ///
126     /// See_Also:
127     ///     For keyboard codes, see `KeyboardIO`.
128     ///     For mouse codes, see `MouseIO`.
129     int event;
130 
131 }
132 
133 /// Represents an event coming from an input device, like a pressed key, button or a gesture.
134 ///
135 /// This only covers events with binary outcomes: the source of event is active, or it is not.
136 /// Analog sources like joysticks may be translated into input events but they won't be precise.
137 struct InputEvent {
138 
139     /// Code uniquely identifying the source of the event, such as a key, button or gesture.
140     InputEventCode code;
141 
142     /// Set to true if the event should trigger an input action.
143     ///
144     /// An input event should be emitted every frame the corresponding button or key is held down, but it will
145     /// only be "active" for one of the frames. The one active frame determines when input actions that derive
146     /// from the event will be fired.
147     ///
148     /// For a keyboard key, this will be the first frame the key is held (when it is pressed). For a mouse button,
149     /// this will be the last frame (when it is released).
150     bool isActive;
151 
152 }
153 
154 /// This is a base interface for nodes that respond to input actions. While `ActionIO` shouldn't interact
155 /// with nodes directly, input handling systems like `FocusIO` or `HoverIO` will expect nodes to implement
156 /// this interface if they support input actions.
157 interface Actionable {
158 
159     /// Determine if the node can currently handle input.
160     ///
161     /// Blocking input changes behavior of I/O systems responsible for passing the node input data:
162     ///
163     /// * A blocked node should NOT have input events called. It is illegal to call `actionImpl`. Input method
164     ///   and device-specific handlers like `hoverImpl` and `focusImpl` usually won't be called either.
165     /// * If the input method has a node selection method like focus or hover, nodes that block input should be
166     ///   excluded from selection. If a node starts blocking while already selected may continue to be selected.
167     ///
168     /// Returns:
169     ///     True if the node "blocks" input — it cannot accept input events, nor focus.
170     ///     False if the node accepts input, and operates like normal.
171     bool blocksInput() const;
172 
173     /// Handle an input action.
174     ///
175     /// This method should not be called for nodes for which `blocksInput` is true.
176     ///
177     /// Params:
178     ///     io       = I/O input handling system to trigger the action, for example `HoverIO` or `FocusIO`.
179     ///         May be null.
180     ///     number   = Number assigned by the I/O system. May be used to fetch a resource from the I/O system if it
181     ///         supported.
182     ///     action   = ID of the action to handle.
183     ///     isActive = If true, this is an active action.
184     ///         Most event handlers is only interested in active handlers;
185     ///         they indicate the event has changed state (just pressed, or just released),
186     ///         whereas an inactive action merely means the button or key is down.
187     /// Returns:
188     ///     True if the action was handled, false if not.
189     bool actionImpl(IO io, int number, immutable InputActionID action, bool isActive)
190     in (!blocksInput, "This node currently doesn't accept input.");
191 
192     /// Memory safe and `const` object comparison.
193     /// Returns:
194     ///     True if this, and the other object, are the same object.
195     /// Params:
196     ///     other = Object to compare to.
197     bool opEquals(const Object other) const;
198 
199     mixin template enableInputActions() {
200 
201         import fluid.future.context : IO;
202         import fluid.io.action : InputActionID;
203 
204         override bool actionImpl(IO io, int number, immutable InputActionID action, bool isActive) {
205 
206             import fluid.io.action : runInputActionHandler;
207 
208             return runInputActionHandler(this, io, number, action, isActive);
209 
210         }
211 
212     }
213 
214 }
215 
216 /// Template with a set of functions for creating input event and input event codes
217 /// from an enum. It can be used as a `mixin` in device nodes; see `MouseIO` and `KeyboardIO`
218 /// for sample usage.
219 template inputEvents(LocalIO, Event)
220 if (is(LocalIO : IO) && is(Event == enum)) {
221 
222     /// Get the input event code from an enum member.
223     /// Params:
224     ///     event = Code for this event.
225     /// Returns:
226     ///     Input event code that can be used for input event routines.
227     static InputEventCode getCode(Event event) {
228         return InputEventCode(ioID!LocalIO, event);
229     }
230 
231     /// A shortcut for getting input event codes that are known at compile time. Handy for
232     /// tests.
233     /// Returns: A struct with event code for each member, corresponding to members of
234     /// `Button`.
235     static codes() {
236         static struct Codes {
237             InputEventCode opDispatch(string name)() {
238                 return getCode(__traits(getMember, Event, name));
239             }
240         }
241         return Codes();
242     }
243 
244     /// A shortcut for getting input events that are known at compile time. Handy for tests.
245     /// Params:
246     ///     isActive = True if the generated input event should be active. Defaults to `false`
247     ///         for `hold`; is `true` for `click`.
248     /// Returns: A mouse button input event.
249     static click() {
250         return hold(true);
251     }
252 
253     /// Create a mouse input event that can be passed to an `ActionIO` handler.
254     ///
255     /// Params:
256     ///     event    = Event type.
257     ///     isActive = True if the event is "active" and should trigger input actions.
258     /// Returns:
259     ///     The created input event.
260     static InputEvent createEvent(Event event, bool isActive = true) {
261         const code = getCode(event);
262         return InputEvent(code, isActive);
263     }
264 
265     /// ditto
266     static hold(bool isActive = false) {
267         static struct Codes {
268             bool isActive;
269             InputEvent opDispatch(string name)() {
270                 return createEvent(
271                     __traits(getMember, Event, name),
272                     isActive);
273             }
274         }
275         return Codes(isActive);
276     }
277 
278 }
279 
280 /// Cast the node to given type if it accepts input using the given method.
281 ///
282 /// In addition to performing a dynamic cast, this checks if the node currently accepts input.
283 /// If it doesn't (for example, if the node is disabled), it will fail the cast.
284 ///
285 /// Params:
286 ///     node = Node to cast.
287 /// Returns:
288 ///     Node casted to the given type, or null if the node can't be casted, or it doesn't support given input method.
289 inout(T) castIfAcceptsInput(T : Actionable)(inout Object node) {
290 
291     // Perform the cast
292     if (auto actionable = cast(inout T) node) {
293 
294         // Fail if the node blocks input
295         if (actionable.blocksInput) {
296             return null;
297         }
298 
299         return actionable;
300 
301     }
302 
303     return null;
304 
305 }
306 
307 /// Get the ID of an input action.
308 /// Params:
309 ///     action = Action to get the ID of.
310 /// Returns:
311 ///     `InputActionID` struct with the action encoded.
312 InputActionID inputActionID(alias action)() {
313 
314     return InputActionID.from!action;
315 
316 }
317 
318 /// Check if the given symbol defines an input action.
319 ///
320 /// The symbol symbol must be a member of an enum marked with `@InputAction`. The enum $(B must not) be a manifest
321 /// constant (eg. `enum foo = 123;`).
322 template isInputAction(alias action) {
323 
324     // Require the action type to be an enum
325     static if (is(typeof(action) == enum)) {
326 
327         // Search through the enum's attributes
328         static foreach (attribute; __traits(getAttributes, typeof(action))) {
329 
330             // Not yet found
331             static if (!is(typeof(isInputAction) == bool)) {
332 
333                 // Check if this is the attribute we're looking for
334                 static if (__traits(isSame, attribute, InputAction)) {
335 
336                     enum isInputAction = true;
337 
338                 }
339 
340             }
341 
342         }
343 
344     }
345 
346     // Not found
347     static if (!is(typeof(isInputAction) == bool)) {
348 
349         // Respond as false
350         enum isInputAction = false;
351 
352     }
353 
354 }
355 
356 enum isRetrievableResource(T) = __traits(compiles, T.fetch(IO.init, 0));
357 
358 /// Helper function to run an input action handler through one of the possible overloads.
359 ///
360 /// Params:
361 ///     action  = Evaluated input action type.
362 ///         Presently, this is an enum member of the input action it comes from.
363 ///         `InputActionID` cannot be used here.
364 ///     handler = Handler for the action.
365 ///         The handler may choose to return a boolean,
366 ///         indicating if it handled (true) or ignored the action (false).
367 ///
368 ///         It may also optionally accept the input action enum, for example `FluidInputAction`,
369 ///         if all of its events are bound to its members (like `FluidInputAction.press`).
370 /// Returns:
371 ///     True if the handler responded to this action, false if not.
372 bool runInputActionHandler(T)(T action, bool delegate(T action) @safe handler)
373 if (isInputAction!action) {
374     return handler(action);
375 }
376 
377 /// ditto
378 bool runInputActionHandler(T)(T action, void delegate(T action) @safe handler)
379 if (isInputAction!action) {
380     handler(action);
381     return true;
382 }
383 
384 /// ditto
385 bool runInputActionHandler(T)(T, bool delegate() @safe handler) {
386     return handler();
387 }
388 
389 /// ditto
390 bool runInputActionHandler(T)(T, void delegate() @safe handler) {
391     handler();
392     return true;
393 }
394 
395 /// Uniform wrapper over the varied set of input action handler variants.
396 ///
397 /// This is an alternative, newer set of overloads for handling input actions with support for passing event metadata
398 /// through resources.
399 ///
400 /// Params:
401 ///     action  = Evaluated input action type.
402 ///         Presently, this is an enum member of the input action it comes from.
403 ///         `InputActionID` cannot be used here.
404 ///     handler = Handler for the action.
405 ///         The handler may choose to return a boolean,
406 ///         indicating if it handled (true) or ignored the action (false).
407 ///
408 ///         It may also optionally accept the input action enum, for example `FluidInputAction`,
409 ///         if all of its events are bound to its members (like `FluidInputAction.press`).
410 ///
411 ///         It may accept a resource type if the resource has a `static fetch(IO, int)` method.
412 /// Returns:
413 ///     True if the handler responded to this action, false if not.
414 bool runInputActionHandler(T, R)(T action, IO io, int number, bool delegate(T action, R resource) @safe handler)
415 if (isInputAction!action && isRetrievableResource!R) {
416     if (io is null) {
417         return false;
418     }
419     Optional!R resource = R.fetch(io, number);
420     if (resource.empty) {
421         return false;
422     }
423     return handler(action, resource.front);
424 }
425 
426 /// ditto
427 bool runInputActionHandler(T, R)(T action, IO io, int number, void delegate(T action, R resource) @safe handler)
428 if (isInputAction!action && isRetrievableResource!R) {
429     if (io is null) {
430         return false;
431     }
432     Optional!R resource = R.fetch(io, number);
433     if (resource.empty) {
434         return false;
435     }
436     handler(action, resource.front);
437     return true;
438 }
439 
440 /// ditto
441 bool runInputActionHandler(T, R)(T, IO io, int number, bool delegate(R resource) @safe handler)
442 if (isRetrievableResource!R) {
443     if (io is null) {
444         return false;
445     }
446     Optional!R resource = R.fetch(io, number);
447     if (resource.empty) {
448         return false;
449     }
450     return handler(resource.front);
451 }
452 
453 /// ditto
454 bool runInputActionHandler(T, R)(T, IO io, int number, void delegate(R resource) @safe handler)
455 if (isRetrievableResource!R) {
456     if (io is null) {
457         return false;
458     }
459     Optional!R resource = R.fetch(io, number);
460     if (resource.empty) {
461         return false;
462     }
463     handler(resource.front);
464     return true;
465 }
466 
467 /// ditto
468 bool runInputActionHandler(T)(T action, IO, int, bool delegate(T action) @safe handler)
469 if (isInputAction!action) {
470     return handler(action);
471 }
472 
473 /// ditto
474 bool runInputActionHandler(T)(T action, IO, int, void delegate(T action) @safe handler)
475 if (isInputAction!action) {
476     handler(action);
477     return true;
478 }
479 
480 /// ditto
481 bool runInputActionHandler(T)(T, IO, int, bool delegate() @safe handler) {
482     return handler();
483 }
484 
485 /// ditto
486 bool runInputActionHandler(T)(T, IO, int, void delegate() @safe handler) {
487     handler();
488     return true;
489 }
490 
491 /// Run a handler for an input action.
492 /// Params:
493 ///     aggregate = Struct or class with input action handlers.
494 ///     actionID  = ID of the action to run.
495 ///     isActive  = True, if the action has fired, false if it is held.
496 /// Returns:
497 ///     True if there exists a matching input handler, and if it responded
498 ///     to the input action.
499 bool runInputActionHandler(T)(auto ref T aggregate, immutable InputActionID actionID, bool isActive = true) {
500 
501     return runInputActionHandler(aggregate, null, 0, actionID, isActive);
502 
503 }
504 
505 /// Run a handler for an input action.
506 ///
507 /// This is a newer, alternative overload
508 ///
509 /// Params:
510 ///     aggregate = Struct or class with input action handlers.
511 ///     io        = I/O system to emit the action.
512 ///     number    = Number internal to the I/O syste; may be used to fetch additional data.
513 ///     actionID  = ID of the action to run.
514 ///     isActive  = True, if the action has fired, false if it is held.
515 /// Returns:
516 ///     True if there exists a matching input handler, and if it responded
517 ///     to the input action.
518 bool runInputActionHandler(T)(auto ref T aggregate, IO io, int number,
519     immutable InputActionID actionID, bool isActive = true)
520 do {
521 
522     import std.functional : toDelegate;
523 
524     bool handled;
525 
526     // Check every action
527     static foreach (handler; InputActionHandlers!T) {
528 
529         // Run handlers that handle this action
530         if (handler.inputActionID == actionID) {
531 
532             // Run the action if the stroke was performed
533             if (shouldActivateWhileDown!(handler.method) || isActive) {
534 
535                 auto dg = toDelegate(&__traits(child, aggregate, handler.method));
536 
537                 handled = runInputActionHandler(handler.inputAction, io, number, dg) || handled;
538                 // TODO 0.8.0 abandon
539 
540             }
541 
542         }
543 
544     }
545 
546     return handled;
547 
548 }
549 
550 /// Wraps an input action handler.
551 struct InputActionHandler(alias action, alias actionHandler) {
552 
553     /// Symbol handling the action.
554     alias method = actionHandler;
555 
556     /// Type of the handler.
557     alias inputAction = action;
558 
559     static InputActionID inputActionID() {
560 
561         return .inputActionID!action;
562 
563     }
564 
565 }
566 
567 /// Find every input action handler in the given type, and check which input actions it handles.
568 ///
569 /// For every such input handler, this will create an `InputActionHandler` struct.
570 template InputActionHandlers(T) {
571 
572     import std.meta;
573 
574     alias Result = AliasSeq!();
575 
576     // Check each member
577     static foreach (memberName; __traits(allMembers, T)) {
578 
579         static if (!__traits(isDeprecated, __traits(getMember, T, memberName)))
580         static foreach (overload; __traits(getOverloads, T, memberName)) {
581 
582             // Find the matching action
583             static foreach (i, actionType; __traits(getAttributes, overload)) {
584 
585                 // Input action — add to the result
586                 static if (isInputActionType!actionType) {
587 
588                     Result = AliasSeq!(
589                         Result,
590                         InputActionHandler!(__traits(getAttributes, overload)[i], overload)
591                     );
592 
593                 }
594 
595                 // Prevent usage via @InputAction
596                 else static if (is(typeof(actionType)) && isInstanceOf!(typeof(actionType), InputAction)) {
597 
598                     static assert(false,
599                         format!"Please use @(%s) instead of @InputAction!(%1$s)"(actionType.type));
600 
601                 }
602 
603             }
604 
605         }
606 
607     }
608 
609     alias InputActionHandlers = Result;
610 
611 }