Skip to content

Commit

Permalink
feat: add mouseButtons reactive variables
Browse files Browse the repository at this point in the history
  • Loading branch information
verekia committed Mar 30, 2024
1 parent acdc35b commit ee6fb9e
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 88 deletions.
128 changes: 40 additions & 88 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Mana Potion supports React, Vue, Svelte, and vanilla JavaScript. It is a particu

The library consists of:

- [**Listeners and a reactive store for inputs and browser state**](#listeners-and-reactive-store)
- [**Listeners and a reactive store for inputs and browser state**](#getting-started)
- [**Animation loops**](#animation-loops)
- [**Headless virtual joysticks**](#virtual-joysticks)
- [**Browser API helpers**](#browser-api-helpers)
Expand All @@ -22,36 +22,20 @@ The library consists of:

## Installation

- If you use React Three Fiber, install `@manapotion/r3f`
- If you use React _without R3F_, install `@manapotion/react`
- If you use Vue, install `@manapotion/vue`
- If you use Svelte, install `@manapotion/svelte`
- If you use **React Three Fiber**, install `@manapotion/r3f`
- If you use **React** _without R3F_, install `@manapotion/react`
- If you use **Vue**, install `@manapotion/vue`
- If you use **Svelte**, install `@manapotion/svelte`
- If you don't use these frameworks, install `@manapotion/vanilla`

## Listeners and reactive store
## Getting started

At the heart of Mana Potion is a [Zustand](https://github.com/pmndrs/zustand) store that gets updated by event listeners. You can access this store imperatively in your animation loop or reactively in your components.
Add `<Listeners />` somewhere in your app:

The listeners available are:

- `<MouseButtonsListener />`
- `<MouseMoveListener />`
- `<MouseScrollListener />`
- `<KeyboardListener />`
- `<PointerLockListener />`
- `<FullscreenListener />`
- `<ResizeListener />`
- `<DeviceTypeListener />`
- `<ScreenOrientationListener />`
- `<PageVisibilityListener />`
- `<PageFocusListener />`

To enable them all, simply add `<Listeners />` to your app:

**React**
**React, Vue, Svelte**

```jsx
import { Listeners } from '@manapotion/react'
import { Listeners } from '@manapotion/react' // or /vue, /svelte

const App = () => (
<>
Expand All @@ -61,30 +45,6 @@ const App = () => (
)
```

**Vue**

```vue
<script setup lang="ts">
import { Listeners } from '@manapotion/vue'
</script>
<template>
<div>Your game</div>
<Listeners />
</template>
```

**Svelte**

```svelte
<script lang="ts">
import { Listeners } from '@manapotion/svelte'
</script>
<div>Your game</div>
<Listeners />
```

**Vanilla**

```js
Expand All @@ -95,44 +55,32 @@ const unsub = listeners({})
// call unsub() to stop listening
```

This will automatically give you access to some reactive and non-reactive variables.
This will automatically give you access to some reactive and non-reactive variables. If you do not want to listen to every event supported by the library, you can cherry-pick individual listeners (for example, `<MouseMoveListener />` or `<FullscreenListener />`).

🗿 **Non-reactive** variables may be frequently updated and should be accessed imperatively in your main loop:
🗿 **Non-reactive** variables may be frequently updated and should be accessed imperatively in your main loop or in event handlers:

```jsx
import { mp } from '@manapotion/react' // or /vue or /vanilla

const animate = () => {
const { mouseMovementX } = mp()
// Move the camera
}
```
import { mp } from '@manapotion/react' // or /vue, /svelte, or /vanilla

⚡️ **Reactive** variables can similarly be accessed imperatively:

```jsx
const animate = () => {
const { isRightMouseDown } = mp()

if (isRightMouseDown) {
// Some imperative logic
}
const { mouse, keyboard } = mp()
// ...
}
```

Or reactively in components to trigger re-renders:
⚡️ **Reactive** variables can similarly be accessed imperatively, but also reactively in components to trigger re-renders

**React**

There are hooks available for all the reactive variables, but you can also use `useMP` by passing a selector to it:
There are hooks available with various granularity for all the reactive variables. You can either use `useMP` by passing a selector to it, or use more specific hooks directly:

```jsx
import { useMP, useIsLeftMouseDown } from '@manapotion/react'
import { useMP, useMouseButtons, useMouse, useIsRightMouseButtonDown } from '@manapotion/react'

const Component = () => {
const isLeftMouseDown = useIsLeftMouseDown()
const isRightMouseButtonDown = useMP(s => s.mouse.buttons.right)
// or
const isRightMouseDown = useMP(s => s.isRightMouseDown)
const isRightMouseButtonDown = useIsRightMouseButtonDown()

// Some reactive component
return ( /* ... */ )
Expand All @@ -141,30 +89,31 @@ const Component = () => {

**Vue**

All the reactive variables are available as refs, either individually or via `mpRefs`:
The reactive variables are available as refs, either individually or via `mpRefs`:

```vue
<script setup lang="ts">
import { isLeftMouseDown, mpRefs } from '@manapotion/vue'
import { isRightMouseButtonDown, mpRefs } from '@manapotion/vue'
</script>
<template>
<div>{{ isLeftMouseDown }}</div>
<div>{{ mpRefs.isRightMouseDown }}</div>
<div>{{ mpRefs.mouse.buttons.right }}</div>
<!-- or -->
<div>{{ isRightMouseButtonDown }}</div>
</template>
```

**Svelte**

All the reactive variables are available as stores, either individually or via `mpStore`:
The reactive variables are available as stores, either individually or via `mpStore`:

```svelte
<script lang="ts">
import { isLeftMouseDown, mpStore } from '@manapotion/svelte'
import { isRightMouseButtonDown, mpStore } from '@manapotion/svelte'
</script>
<div>{$isLeftMouseDown}</div>
<div>{$mpStore.isRightMouseDown}</div>
<div>{$isRightMouseButtonDown}</div>
<div>{$mpStore.mouse.buttons.right}</div>
```

**Vanilla**
Expand All @@ -175,7 +124,7 @@ There is no reactivity system in vanilla JavaScript, so you can use [callbacks](
import { manaPotionStore } from '@manapotion/vanilla'

const unsub = manaPotionStore.subscribe(state => {
console.log(state.isLeftMouseDown)
console.log(state.mouse.buttons.right)
})
```

Expand All @@ -185,13 +134,13 @@ Legend: ⚡️ **Reactive**, 🗿 **Non-reactive**, 🚧 **Not implemented yet**

### 🌐 Browser

- ⚡️ `isFullscreen`
- ⚡️ `isPageVisible`
- ⚡️ `isPageFocused`
- ⚡️ `isDesktop` / `isMobile`
- ⚡️ `isLandscape` / `isPortrait`
- 🗿 `windowWidth`
- 🗿 `windowHeight`
- ⚡️ `browser.isFullscreen`
- ⚡️ `browser.isPageVisible`
- ⚡️ `browser.isPageFocused`
- ⚡️ `browser.isDesktop` / `browser.isMobile`
- ⚡️ `browser.isLandscape` / `browser.isPortrait`
- 🗿 `browser.windowWidth`
- 🗿 `browser.windowHeight`
- 🚧 `pointerLockSupported`

### 🖱️ Mouse
Expand All @@ -210,6 +159,9 @@ You can import and use `resetMouse` to reinitialize the mouse data.

### ⌨️ Keyboard

- ⚡️ `keyboard.byKey`
- ⚡️ `keyboard.byCode`

⚡️ `keyboard` contains keys that are available in two versions, `byCode` and `byKey`. This lets you decide if you want to use the [physical location](https://developer.mozilla.org/en-US/docs/Web/API/Keyboard_API#writing_system_keys) (`byCode`) of the key or the character being typed as a key (`byKey`). Using the physical location is better for game controls such as using WASD to move a character, because it is agnostic to the user's keyboard layout (did you know French keyboards are not QWERTY but AZERTY?).

Here is how you would handle going forward when the user presses W (or Z on French keyboards):
Expand Down Expand Up @@ -283,7 +235,7 @@ You can provide custom event callbacks to `<Listeners />` or to individual liste
```vue
<Listeners @fullscreenChange="handleFullscreenChange" />
<!-- or -->
<FullscreenListener @fullscreenChange="handleFullscreenChange" />
<FullscreenListener @fullscreen-change="handleFullscreenChange" />
```

**Svelte**
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/react-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ export const useIsMiddleMouseButtonDown = () => useMP(s => s.mouse.buttons.middl
export const useIsRightMouseButtonDown = () => useMP(s => s.mouse.buttons.right)
export const useKeyboard = () => useMP(s => s.keyboard)
export const useMouse = () => useMP(s => s.mouse)
export const useMouseButtons = () => useMP(s => s.mouse.buttons)
1 change: 1 addition & 0 deletions packages/svelte/src/svelte-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ export const isMiddleMouseButtonDown = derived(mpStore, $mpStore => $mpStore.mou
export const isRightMouseButtonDown = derived(mpStore, $mpStore => $mpStore.mouse.buttons.right)
export const keyboard = derived(mpStore, $mpStore => $mpStore.keyboard)
export const mouse = derived(mpStore, $mpStore => $mpStore.mouse)
export const mouseButtons = derived(mpStore, $mpStore => $mpStore.mouse.buttons)
3 changes: 3 additions & 0 deletions packages/vue/src/vue-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const isMiddleMouseButtonDown = ref(manaPotionStore.getState().mouse.butt
export const isRightMouseButtonDown = ref(manaPotionStore.getState().mouse.buttons.right)
export const keyboard = ref(manaPotionStore.getState().keyboard)
export const mouse = ref(manaPotionStore.getState().mouse)
export const mouseButtons = ref(manaPotionStore.getState().mouse.buttons)

export const mpRefs = reactive({
isFullscreen,
Expand All @@ -30,6 +31,7 @@ export const mpRefs = reactive({
isRightMouseButtonDown,
keyboard,
mouse,
mouseButtons,
})

manaPotionStore.subscribe(state => {
Expand All @@ -46,4 +48,5 @@ manaPotionStore.subscribe(state => {
isRightMouseButtonDown.value = state.mouse.buttons.right
keyboard.value = state.keyboard
mouse.value = state.mouse
mouseButtons.value = state.mouse.buttons
})

0 comments on commit ee6fb9e

Please sign in to comment.