From e708ad666e09931c73f77bac2aeefa17b2cf60a4 Mon Sep 17 00:00:00 2001 From: Kamran Ayub Date: Fri, 9 Feb 2024 08:13:31 -0600 Subject: [PATCH] docs: Fix symbol linking + Pointer docs improvements (#2928) Closes # ## Changes: - fix: symbol link handling namespaces/fully qualified symbols - docs: Better Mouse and Touch (formerly, "Pointers") docs using Twoslash snippets and updated for new API --- site/docs/02-fundamentals/04-cameras.mdx | 2 +- .../05-entity-component-system.mdx | 2 +- site/docs/10-physics/08-colliders.mdx | 2 +- site/docs/11-input/10.3-pointer.mdx | 283 +++++++++++------- site/docs/11-input/10.4-gamepad.mdx | 12 +- site/docusaurus.config.ts | 7 +- src/engine/Input/Index.ts | 5 - src/engine/index.ts | 2 + 8 files changed, 204 insertions(+), 111 deletions(-) diff --git a/site/docs/02-fundamentals/04-cameras.mdx b/site/docs/02-fundamentals/04-cameras.mdx index 944748d3e..6082217ba 100644 --- a/site/docs/02-fundamentals/04-cameras.mdx +++ b/site/docs/02-fundamentals/04-cameras.mdx @@ -95,7 +95,7 @@ in-game effects. "Lerp" is short for [Linear Interpolation](http://en.wikipedia.org/wiki/Linear_interpolation) and it enables the camera focus to move smoothly between two points using timing functions. -Use [[Camera.move]] to ease to a specific point using a provided [[EasingFunction]]. +Use [[Camera.move]] to ease to a specific point using a provided [[Util.EasingFunction|EasingFunction]]. ```typescript export interface EasingFunction { diff --git a/site/docs/05-entity-component-system/05-entity-component-system.mdx b/site/docs/05-entity-component-system/05-entity-component-system.mdx index 13a3338d9..42e4b1025 100644 --- a/site/docs/05-entity-component-system/05-entity-component-system.mdx +++ b/site/docs/05-entity-component-system/05-entity-component-system.mdx @@ -26,7 +26,7 @@ pieces live and can interact together. Each [[Scene]] in Excalibur contains an E Inside an ECS [[World]] there is an [[EntityManager]], [[QueryManager]], and [[SystemManager]] that handle all the details. -- [[EntityManager]] - Manages all the entities known by the world, it is an [[Observer]] of entity component changes over time. +- [[EntityManager]] - Manages all the entities known by the world, it is an [[Util.Observer|Observer]] of entity component changes over time. - [[QueryManager]] - Manages all the entity queries, a [[Query]] allows a search of all known entities by a set of component types. - [[SystemManager]] - Manages all the systems known by the world, it updates a [[System]] in priority order (low to high) and gives them a set of entities that match types. diff --git a/site/docs/10-physics/08-colliders.mdx b/site/docs/10-physics/08-colliders.mdx index f5c47dadb..0d8927888 100644 --- a/site/docs/10-physics/08-colliders.mdx +++ b/site/docs/10-physics/08-colliders.mdx @@ -7,7 +7,7 @@ section: Physics Colliders are abstractions over geometry in Excalibur, they implement the [[Collider]] interface and know how to detect intersecions with other colliders, test ray casts, check point containment, etc. Related but not the same are [bodies](/docs/bodies) which are abstractions over the collision response -Colliders attached to an [[Entity]] will have a [[Entity.owner]] populated. +Colliders attached to an [[Entity]] will have a [[Component.owner|owner]] populated. :::note diff --git a/site/docs/11-input/10.3-pointer.mdx b/site/docs/11-input/10.3-pointer.mdx index a9ccf3745..a191d081c 100644 --- a/site/docs/11-input/10.3-pointer.mdx +++ b/site/docs/11-input/10.3-pointer.mdx @@ -1,162 +1,196 @@ --- -title: Pointers +title: Mouse and Touch slug: /pointers section: Input --- -## Mouse and Touch +```twoslash include ex +/// +declare const engine: ex.Engine; +``` + +Excalibur handles mouse and touch input by abstracting event handling into a [[Engine.input|engine.input.pointers]] API that closely follows the [W3C Pointer Events](https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events) spec. Excalibur normalizes both mouse and touch events to a single [[Input.PointerEvent|PointerEvent]] +that your game can subscribe to and handle. -Excalibur handles mouse and touch input using a [[Pointers]] API that closely follows the [W3C Pointer Events](https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events) spec. Excalibur normalizes mouse and touch events to a [[PointerEvent]] -that your game can subscribe to and handle ([[Engine.input|engine.input.pointers]]`). +## The Primary Pointer -There is always at least one [[Pointer]] available ([[Pointers.primary]]) and +There is always at least one [[PointerAbstraction|Pointer]] available ([[Input.PointerEventReceiver.primary|engine.input.pointers.primary]]) and you can request multiple pointers to support multi-touch scenarios. -Since [[Pointers.primary]] normalizes both mouse and touch events, your game +```ts twoslash +// @include: ex +// ---cut--- +engine.input.pointers.primary; +``` + +Since a pointer normalizes both mouse and touch events, your game automatically supports touch for the primary pointer by default. When you handle the events, you can customize what your game does based on the type of pointer, if applicable. -:::note +## Handling Pointer Events Globally + +You can subscribe (or unsubscribe) to pointer events multiple ways using the [[Input.PointerEventReceiver.on|on]], [[Input.PointerEventReceiver.once|once]], or [[Input.PointerEventReceiver.off|off]] methods. Each method takes two arguments: the event name and the handler. -For performance reasons, +```ts twoslash +// @include: ex +// @noErrors +// ---cut--- - +engine.input.pointers.primary.o +// ^| -actors do not automatically capture pointer events - +``` -until they are opted-in +You can subscribe to the primary pointer, all pointers, or a specific pointer. - +```ts twoslash +// @include: ex +// ---cut--- -. +// Subscribe to the primary pointer +engine.input.pointers.primary.on; +// ^? - +// Subscribe to a specific pointer (multi-touch) +engine.input.pointers.at(1).on; +// ^? -::: +// Subscribe to all pointers (advanced) +engine.input.pointers.on; +// ^? +``` -### Pointer Events +### Event Names -You can subscribe to pointer events through `engine.input.pointers.on`. A [[PointerEvent]] object is -passed to your handler which offers information about the pointer input being received. +The event name is a string and can be one of the following: - `down` - When a pointer is pressed down (any mouse button or finger press) - `up` - When a pointer is lifted - `move` - When a pointer moves (be wary of performance issues when subscribing to this) - `cancel` - When a pointer event is canceled for some reason +- `wheel` - When a mousewheel is activated (trackpad scroll or mouse wheel) + +### Event Handlers -```js +A [[Input.PointerEvent|PointerEvent]] object (or in the case of `wheel`, a [[Input.WheelEvent|WheelEvent]]) is +passed to your handler which offers information about the pointer input being received. + +```ts twoslash +// @include: ex +// ---cut--- + +// Subscribe to the primary pointer engine.input.pointers.primary.on('down', function (evt) {}); +// ^? engine.input.pointers.primary.on('up', function (evt) {}); engine.input.pointers.primary.on('move', function (evt) {}); engine.input.pointers.primary.on('cancel', function (evt) {}); -``` - -#### Wheel Event - -You can also subscribe to the mouse wheel event through `engine.input.pointers.on`. A [[WheelEvent]] -object is passed to your handler which offers information about the wheel event being received. - -- `wheel` - When a mousewheel is activated (trackpad scroll or mouse wheel) - -```js +engine.input.pointers.primary.on('wheel', function (evt) {}); +// ^? + +// Subscribe to a specific pointer (multi-touch) +engine.input.pointers.at(1).on('down', function (evt) {}); +engine.input.pointers.at(1).on('up', function (evt) {}); +engine.input.pointers.at(1).on('move', function (evt) {}); +engine.input.pointers.at(1).on('cancel', function (evt) {}); +engine.input.pointers.at(1).on('wheel', function (evt) {}); + +// Subscribe to all pointers (advanced) +engine.input.pointers.on('down', function (evt) {}); +engine.input.pointers.on('up', function (evt) {}); +engine.input.pointers.on('move', function (evt) {}); +engine.input.pointers.on('cancel', function (evt) {}); engine.input.pointers.on('wheel', function (evt) {}); ``` -### Last position querying +### Querying the Last Position -If you don't wish to subscribe to events, you can also access the [[Pointer.lastPagePos]], [[Pointer.lastScreenPos]] -or [[Pointer.lastWorldPos]] coordinates ([[Vector]]) on the pointer you're targeting. +If you don't wish to subscribe to events, you can also access the [[PointerAbstraction.lastPagePos]], [[PointerAbstraction.lastScreenPos]] +or [[PointerAbstraction.lastWorldPos]] coordinates ([[Vector]]) on the pointer you're targeting. -```js +```ts twoslash +// @include: ex +// ---cut--- engine.input.pointers.primary.lastPagePos; engine.input.pointers.primary.lastScreenPos; engine.input.pointers.primary.lastWorldPos; ``` -Note that the value may be `null` if the Pointer was not active the last frame. - -### Pointer scope (window vs. canvas) +:::note +The value may be `null` if the Pointer was not active the last frame. +::: -You have the option to handle _all_ pointer events in the browser by setting -[[EngineOptions.pointerScope]] to [[PointerScope.Document]]. If this is enabled, +### Scoping Events to Canvas vs. Document -Excalibur will handle every pointer event in the browser. This is useful for handling -complex input and having control over every interaction. +You can customize how pointer events are handled by setting the [[EngineOptions.pointerScope]] option when creating a new [[Engine]] instance, like this: -You can also use [[PointerScope.Canvas]] to only scope event handling to the game -canvas. This is useful if you don't care about events that occur outside the game. +```ts twoslash +// @noErrors +/// +// ---cut--- +const game = new ex.Engine({ + pointerScope: ex.Input.PointerScope.C +// ^| +}); +``` -One real-world example is dragging and gestures. Sometimes a player will drag their -finger outside your game and then into it, expecting it to work. If [[PointerScope]] -is set to [[PointerScope.Canvas|Canvas]] this will not work. If it is set to -[[PointerScope.Document|Document]], it will. +The default scope is [[Input.PointerScope.Canvas|PointerScope.Canvas]]. For many games, this works really well. It scopes event handling to the game canvas. -### Responding to input +This is useful if you don't care about events that occur outside the game. -The primary pointer can be a mouse, stylus, or single finger touch event. You -can inspect what type of pointer it is from the [[PointerEvent]] handled. +You also have the option to handle _all_ pointer events in the browser window by using [[Input.PointerScope.Document|PointerScope.Document]], like this: -```js -engine.input.pointers.primary.on('down', function (pe) { - if (pe.pointerType === ex.Input.PointerType.Mouse) { - ex.Logger.getInstance().info('Mouse event:', pe); - } else if (pe.pointerType === ex.Input.PointerType.Touch) { - ex.Logger.getInstance().info('Touch event:', pe); - } +```ts twoslash +// @noErrors +/// +// ---cut--- +const game = new ex.Engine({ + pointerScope: ex.Input.PointerScope.D +// ^| }); ``` -### Multiple Pointers (Multi-Touch) +This is useful for handling complex input and having control over every interaction. -When there is more than one pointer detected on the screen, -this is considered multi-touch. For example, pressing one finger, -then another, will create two pointers. If you lift a finger, -the first one remains and the second one disappears. +:::note When to Use Document Scope -You can handle multi-touch by subscribing to however many pointers -you would like to support. If a pointer doesn't yet exist, it will -be created. You do not need to check if a pointer exists. If it does -exist, it will propagate events, otherwise it will remain idle. +One real-world example is dragging and gestures. Sometimes a player will drag their +finger outside your game and then into it, expecting it to work. If [[Input.PointerScope|PointerScope]] +is set to [[Input.PointerScope.Canvas|Canvas]] this will not work. If it is set to +[[Input.PointerScope.Document|Document]], it will. -Excalibur does not impose a limit to the amount of pointers you can -subscribe to, so by all means, support all 10 fingers. +::: -_Note:_ There is no way to identify touches after they happen; you can only -know that there are _n_ touches on the screen at once. +:::info What about HTML elements? +`PointerScope.Canvas` does not affect handling native HTML events like `click`, `mouseover`, `mouseout`, etc. on any HTML-based UI. It only applies to pointer events that are targeted to the HTML `` element Excalibur uses to render your game. -```js -function paint(color) { - // create a handler for the event - return function (pe) { - if (pe.pointerType === ex.Input.PointerType.Touch) { - engine.canvas.fillStyle = color; - engine.canvas.fillRect(pe.x, pe.y, 5, 5); - } - }; -} -engine.input.pointers.at(0).on('move', paint('blue')); // 1st finger -engine.input.pointers.at(1).on('move', paint('red')); // 2nd finger -engine.input.pointers.at(2).on('move', paint('green')); // 3rd finger -``` +However, `PointerScope.Document` will handle all pointer events in the browser window, including those targeted to HTML elements. If you overlay any HTML elements on top of your game, be aware that pointer events will also be handled by Excalibur. +::: + +## Handling Pointer Events on Actors -### Actor Pointer Events +[[Actor|Actors]] can handle pointer events targeted to them. By default, the collision geometry is used to test whether the pointer is +in or out of the actor. -Actors will participate in pointer events, by default it uses the collision geometry to test whether the pointer is -in or out of the actor. In the example below the actor has default box geometry of 100x100. +In the example below the actor has default box geometry of 100x100: -```typescript +```ts twoslash +// @include: ex +// ---cut--- class MyActor extends ex.Actor { constructor() { super({ pos: ex.vec(200, 200), width: 100, - height: 100}); + height: 100 + }); + this.on('pointerenter', () => { console.log('enter') }); + this.on('pointerleave', () => { console.log('leave') }); @@ -168,11 +202,17 @@ class MyActor extends ex.Actor { If your [[Actor]] has no geometry and only graphics you will need to enable graphics bounds testing. ::: -To enable graphics bounds testing for pointer events you can grab the [[Actor]]'s [[PointerComponent]] +### Graphics Bounds Testing + +Rather than using the collision geometry, you can use the graphics bounds to test whether the pointer is in or out of the actor. This is useful when you have an actor with no collision geometry and only graphics, or when the graphics have custom dimensions that don't match the collision geometry. -```typescript +To enable graphics bounds testing for pointer events you can grab the [[Actor]]'s [[Input.PointerComponent|PointerComponent]] with `this.pointer` and set `useGraphicsBounds` to `true`. + +```ts twoslash {4} +// @include: ex +// ---cut--- class MyGraphicsActor extends ex.Actor { - constructor(image: ImageSource) { + constructor(image: ex.ImageSource) { super({pos: ex.vec(200, 200)}); this.pointer.useGraphicsBounds = true; this.graphics.use(image.toSprite()); @@ -188,8 +228,7 @@ class MyGraphicsActor extends ex.Actor { ``` - -#### Actor Events +### Actor Pointer Event List Actors have the following **extra** events you can subscribe to: @@ -199,13 +238,55 @@ Actors have the following **extra** events you can subscribe to: - `pointerdragmove` - When a pointer drags an actor - `pointerdragend` - When a pointer ends a drag on an actor -## Gamepads and Controllers +## Checking the Pointer Type + +The primary pointer can be a mouse, stylus, or single finger touch event. You +can inspect what type of pointer it is from the [[Input.PointerEvent|PointerEvent]] handled. + +```ts twoslash +// @include: ex +// ---cut--- +engine.input.pointers.primary.on('down', function (pe: ex.Input.PointerEvent) { + if (pe.pointerType === ex.Input.PointerType.Mouse) { + ex.Logger.getInstance().info('Mouse event:', pe); + } else if (pe.pointerType === ex.Input.PointerType.Touch) { + ex.Logger.getInstance().info('Touch event:', pe); + } +}); +``` + +## Multiple Pointers (Multi-Touch) -You can query any [[Gamepad|Gamepads]] that are connected or listen to events ("button" and "axis"). +When there is more than one pointer detected on the screen, +this is considered multi-touch. For example, pressing one finger, +then another, will create two pointers. If you lift a finger, +the first one remains and the second one disappears. -You must opt-in to controller support ([[Gamepads.enabled]]) because it is a polling-based -API, so we have to check it each update frame. If an gamepad related event handler is set, you will -automatically opt-in to controller polling. +You can handle multi-touch by subscribing to however many pointers +you would like to support. If a pointer doesn't yet exist, it will +be created. You do not need to check if a pointer exists. If it does +exist, it will propagate events, otherwise it will remain idle. -HTML5 Gamepad API only supports a maximum of 4 gamepads. You can access them using the [[Gamepads.at]] method. If a [[Gamepad]] is -not connected, it will simply not throw events. +Excalibur does not impose a limit to the amount of pointers you can +subscribe to, so by all means, support all 10 fingers. + +:::note +There is no way to identify touches after they happen; you can only +know that there are _n_ touches on the screen at once. +::: + +```ts twoslash +// @include: ex +// ---cut--- +function paint(color: ex.Color) { + // create a handler for the event + return function (pe: ex.Input.PointerEvent) { + if (pe.pointerType === ex.Input.PointerType.Touch) { + engine.graphicsContext.drawRectangle(pe.worldPos, 5, 5, color); + } + }; +} +engine.input.pointers.at(0).on('move', paint(ex.Color.Blue)); // 1st finger +engine.input.pointers.at(1).on('move', paint(ex.Color.Red)); // 2nd finger +engine.input.pointers.at(2).on('move', paint(ex.Color.Green)); // 3rd finger +``` diff --git a/site/docs/11-input/10.4-gamepad.mdx b/site/docs/11-input/10.4-gamepad.mdx index 08b513457..225ba9e7b 100644 --- a/site/docs/11-input/10.4-gamepad.mdx +++ b/site/docs/11-input/10.4-gamepad.mdx @@ -1,9 +1,19 @@ --- -title: Gamepad +title: Gamepads and Controllers slug: /gamepad section: Input --- +You can query any [[Input.Gamepad|Gamepads]] that are connected or listen to events ("button" and "axis"). + +You must opt-in to controller support ([[Input.Gamepads.enabled|engine.inputs.gamepads.enabled]]) because it is a polling-based +API, so we have to check it each update frame. If an gamepad related event handler is set, you will +automatically opt-in to controller polling. + +HTML5 Gamepad API only supports a maximum of 4 gamepads. You can access them using the [[Input.Gamepads.at|engine.input.gamepads.at]] method. If a [[Input.Gamepad|Gamepad]] is +not connected, it will simply not throw events. + + ### Gamepad Filtering Different browsers/devices are sometimes loose about the devices they consider Gamepads, you can set minimum device requirements with diff --git a/site/docusaurus.config.ts b/site/docusaurus.config.ts index c5f6d57c7..5ab05ba66 100644 --- a/site/docusaurus.config.ts +++ b/site/docusaurus.config.ts @@ -327,7 +327,12 @@ function buildSymbolLink(symbolPath: string, basePath: string, symbolLinkIndex: .concat([]) .reverse() .find(([, kind]) => SYMBOL_CONTAINERS.includes(kind)) || [undefined, undefined]; - const [, containerKind] = lastContainer; + const moduleContainer = symbolMatches.find(([, kind]) => kind === ReflectionKind.SomeModule || kind === ReflectionKind.Module || kind === ReflectionKind.Namespace); + let [, containerKind] = lastContainer; + + if (moduleContainer) { + containerKind = moduleContainer[1]; + } let containerPath; diff --git a/src/engine/Input/Index.ts b/src/engine/Input/Index.ts index 65a1af251..8b9f63e7b 100644 --- a/src/engine/Input/Index.ts +++ b/src/engine/Input/Index.ts @@ -1,11 +1,6 @@ // This import site is deprecated // TODO remove deprecated exports in v0.29.0 -/** - * @module - * Provides support for mice, keyboards, and controllers. - */ - export { /** * @deprecated ex.Input.WheelEvent import site will be removed in v0.29.0, use ex.WheelEvent diff --git a/src/engine/index.ts b/src/engine/index.ts index d475c66cb..7b8f39b49 100644 --- a/src/engine/index.ts +++ b/src/engine/index.ts @@ -90,6 +90,7 @@ export { PointerEventReceiver } from './Input/PointerEventReceiver'; +export { PointerAbstraction } from './Input/PointerAbstraction'; export { PointerComponent } from './Input/PointerComponent'; export { PointerSystem } from './Input/PointerSystem'; export { PointerType } from './Input/PointerType'; @@ -113,6 +114,7 @@ export { KeyboardInitOptions, Keyboard } from './Input/Keyboard'; +export * from './Input/InputHost'; export * from './Input/InputMapper'; // ex.Util namespaces