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 }