Input/README.md

455 lines
11 KiB
Markdown

# @workshack/input
Workshack input components library with gamepad and keyboard navigation support for Angular applications.
## Installation
```bash
npm install @workshack/input
```
## Services
### InputService
**Description:** Core service for managing input devices, actions, and schemes.
**Usage:**
```typescript
import { InputService } from '@workshack/input';
@Component({
// ...
})
export class MyComponent {
constructor(private inputService: InputService) {
// Define custom actions
const jumpAction = this.inputService.defineAction('jump');
jumpAction.onDown(() => console.log('Jump pressed!'));
jumpAction.onUp(() => console.log('Jump released!'));
// Create input scheme
const gameScheme = this.inputService.defineScheme('game');
// Listen for device changes
this.inputService.deviceListChanged.subscribe(devices => {
console.log('Available devices:', devices);
});
}
}
```
**Methods:**
- `defineAction(name: string): InputAction` - Creates a new input action
- `defineScheme(name: string): InputScheme` - Creates a new input scheme
- `getDevices(): InputDevice[]` - Returns list of available input devices
**Properties:**
- `actions: InputAction[]` - Array of defined actions
- `schemes: InputScheme[]` - Array of defined schemes
- `devices: InputDevice[]` - Array of connected devices
- `deviceListChanged: BehaviorSubject<InputDevice[]>` - Observable for device changes
---
### ControlService (Example Integration)
**Description:** Higher-level service that integrates with InputService to provide common navigation actions.
**Usage:**
```typescript
import { ControlService } from 'your-app/services/control.service';
@Component({
// ...
})
export class MyComponent {
constructor(private controlService: ControlService) {
// Service automatically sets up common actions:
// - Left, Right, Up, Down navigation
// - Accept, Back, Exit actions
// - Hint/Help action
// Get key names for display
const keyNames = this.controlService.getKeyNames('Enter', 'Escape');
console.log('Key names:', keyNames); // ['Enter', 'Escape'] or themed names
}
}
```
**Static Properties:**
- `keyLeft: InputAction` - Left navigation action
- `keyRight: InputAction` - Right navigation action
- `keyUp: InputAction` - Up navigation action
- `keyDown: InputAction` - Down navigation action
- `keyAccept: InputAction` - Accept/confirm action
- `keyBack: InputAction` - Back/cancel action
- `keyExit: InputAction` - Exit action
- `keyHint: InputAction` - Hint/help action
**Methods:**
- `getKeyNames(...names): string[]` - Get themed key names for display
- `clearKeyBindings()` - Clear all key bindings from schemes
- `bindActionsToScheme(device: InputDevice)` - Bind actions to device scheme
## Directives
### ActionBindingDirective
**Selector:** `[inputActionBinding]`
**Description:** Binds an action name to an HTML element.
**Usage:**
```html
<button inputActionBinding="confirm">Confirm</button>
<div inputActionBinding="back">Back</div>
```
**Properties:**
- `inputActionBinding: string` - Action name to bind to the element
**Features:**
- Automatically sets `action` attribute on the element
- Works with keyboard and gamepad navigation
---
### KeyItemDirective
**Selector:** `[inputKeyItem]`
**Description:** Makes an element navigable with keyboard/gamepad and handles focus states.
**Usage:**
```html
<button
inputKeyItem
[active]="'focused'"
[actionBindings]="{
Accept: () => onClick(),
Back: () => onCancel()
}"
(activated)="onActivated()"
(focused)="onFocused()"
(blurred)="onBlurred()">
Click me
</button>
```
**Properties:**
- `active: string` - CSS class name for active/focused state (default: 'active')
- `actionBindings: ActionBindings` - Object mapping action names to callback functions
**Events:**
- `activated: EventEmitter<void>` - Emitted when item is activated
- `deactivated: EventEmitter<void>` - Emitted when item is deactivated
- `focused: EventEmitter<void>` - Emitted when item receives focus
- `blurred: EventEmitter<void>` - Emitted when item loses focus
**Static Methods:**
- `FireAction(action: string): boolean` - Fires action on currently focused item
- `ActivateCurrentItem()` - Activates the currently focused item
- `SelectNexItem(fromAngle: number, toAngle: number)` - Selects next item in direction
---
### KeyItemGroupDirective
**Selector:** `[inputKeyItemGroup]`
**Description:** Groups KeyItem elements for scoped navigation.
**Usage:**
```html
<div inputKeyItemGroup>
<button inputKeyItem>Button 1</button>
<button inputKeyItem>Button 2</button>
<button inputKeyItem>Button 3</button>
</div>
```
**Features:**
- Navigation is scoped to items within the group
- Automatically manages focus within the group
- Supports nested groups
## Models
### InputAction
**Description:** Represents a bindable input action (e.g., "jump", "fire", "menu").
**Usage:**
```typescript
const jumpAction = new InputAction('jump');
// Bind callbacks
jumpAction.onDown((value, data, angle, timePressed) => {
console.log('Jump started!', { value, data, angle, timePressed });
});
jumpAction.onUp((value, data, angle, timePressed) => {
console.log('Jump ended!', { value, data, angle, timePressed });
});
jumpAction.onChange((value, data, angle, timePressed) => {
console.log('Jump value changed:', value);
});
// Fire the action
jumpAction.fire(1.0); // Press
jumpAction.fire(0.0); // Release
```
**Methods:**
- `onDown(callback: ActionCallback)` - Register callback for action press
- `onUp(callback: ActionCallback)` - Register callback for action release
- `onChange(callback: ActionCallback)` - Register callback for value changes
- `fire(value: number, data?: unknown, angle?: number, timePressed?: number)` - Fire the action
- `mapKeysToScheme(scheme: InputScheme, keys: (string|number)[])` - Map keys to scheme
---
### InputDevice
**Description:** Base class for input devices (keyboard, gamepad).
**Properties:**
- `name: string` - Device name
- `type: string` - Device type ('keyboard', 'gamepad')
**Methods:**
- `hasScheme(): boolean` - Check if device has an input scheme
- `mapKey(action: InputAction, keys: (string|number)[])` - Map keys to action
---
### InputKeyboard
**Description:** Keyboard input device implementation.
**Usage:**
```typescript
const keyboard = new InputKeyboard('Main Keyboard');
keyboard.mapKey(jumpAction, ['Space', 'KeyW']);
```
---
### InputGamepad
**Description:** Gamepad input device implementation with automatic detection.
**Static Properties:**
- `DeviceConnected: Subject<InputGamepad>` - Observable for gamepad connections
- `DeviceDisconnected: Subject<InputGamepad>` - Observable for gamepad disconnections
**Static Methods:**
- `Init()` - Initialize gamepad detection
**Usage:**
```typescript
// Listen for gamepad events
InputGamepad.DeviceConnected.subscribe(gamepad => {
console.log('Gamepad connected:', gamepad.name);
gamepad.mapKey(jumpAction, [0]); // Map to button 0
});
InputGamepad.DeviceDisconnected.subscribe(gamepad => {
console.log('Gamepad disconnected:', gamepad.name);
});
// Initialize detection
InputGamepad.Init();
```
## Complete Example
```typescript
import { Component, OnInit } from '@angular/core';
import {
InputService,
NavigationModule,
KeyItemDirective,
KeyItemGroupDirective,
ActionBindingDirective
} from '@workshack/input';
@Component({
selector: 'app-game-menu',
imports: [
NavigationModule,
KeyItemDirective,
KeyItemGroupDirective,
ActionBindingDirective
],
template: `
<div class="game-menu" inputKeyItemGroup>
<h2>Game Menu</h2>
<button
inputKeyItem
[actionBindings]="{
Accept: () => startGame(),
Back: () => goBack()
}"
(focused)="playHoverSound()"
class="menu-item">
Start Game
</button>
<button
inputKeyItem
[actionBindings]="{
Accept: () => showOptions(),
Back: () => goBack()
}"
(focused)="playHoverSound()"
class="menu-item">
Options
</button>
<button
inputKeyItem
[actionBindings]="{
Accept: () => exitGame(),
Back: () => goBack()
}"
(focused)="playHoverSound()"
class="menu-item">
Exit
</button>
<div class="controls-hint">
<span>Use arrow keys or gamepad to navigate</span>
<span>Press Enter or A to select</span>
</div>
</div>
`,
styles: [`
.game-menu {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
padding: 2rem;
}
.menu-item {
padding: 1rem 2rem;
background: #333;
color: white;
border: 2px solid transparent;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
}
.menu-item.active,
.menu-item:hover {
background: #555;
border-color: #007bff;
transform: scale(1.05);
}
.controls-hint {
display: flex;
flex-direction: column;
text-align: center;
font-size: 0.8rem;
color: #666;
margin-top: 2rem;
}
`]
})
export class GameMenuComponent implements OnInit {
constructor(private inputService: InputService) {}
ngOnInit() {
// Set up custom actions
const menuAction = this.inputService.defineAction('menu');
menuAction.onUp(() => this.toggleMenu());
// Listen for device changes
this.inputService.deviceListChanged.subscribe(devices => {
console.log('Input devices:', devices.map(d => d.name));
});
}
startGame() {
console.log('Starting game...');
}
showOptions() {
console.log('Showing options...');
}
exitGame() {
console.log('Exiting game...');
}
goBack() {
console.log('Going back...');
return false; // Prevent default navigation
}
playHoverSound() {
// Play hover sound effect
console.log('Hover sound');
}
toggleMenu() {
console.log('Toggle menu');
}
}
```
## Features
- 🎮 **Gamepad Support** - Automatic detection and mapping for standard gamepads
- ⌨️ **Keyboard Navigation** - Full keyboard navigation with customizable key bindings
- 🖱️ **Mouse/Touch Support** - Works seamlessly with mouse and touch interactions
- 🎯 **Action Binding System** - Flexible system for binding actions to inputs
- 📐 **Directional Navigation** - Smart directional navigation based on element positions
- 🎨 **Customizable Themes** - Support for different key name themes (PlayStation, Xbox, etc.)
- 🔄 **Hot-plugging** - Automatic detection of connected/disconnected devices
- 🎛️ **Multiple Schemes** - Support for multiple input schemes per device
## Development
### Building
```bash
npm run build:input
```
### Watching for changes
```bash
npm run watch:input
```
### Testing
```bash
npm run test:input
```
### Publishing
```bash
npm run publish:input
```
## License
This library is licensed under a custom license that allows:
- **Free use** for non-commercial applications (no revenue, no ads, no paid features)
- **Commercial use** with attribution requirement - you must credit the library in your app (e.g., on an "About" or "For Developers" page)
For full license terms, see the [LICENSE](LICENSE) file.
### Attribution Example for Commercial Use
```
This application uses Workshack Input Library
(https://www.npmjs.com/package/@workshack/input)
© 2025 Workshack Team
```