Skip to content

Commit 667d6eb

Browse files
authored
Merge branch 'main' into fix/content-entities-proxy-immutable-headers
2 parents 7fc6fda + eaa1fa5 commit 667d6eb

13 files changed

Lines changed: 344 additions & 8 deletions

File tree

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { Entity, IEngine, LastWriteWinElementSetComponentDefinition } from '../../engine'
2+
import { TouchScreenControls, PBTouchScreenControls, PBTouchScreenControls_TouchInput } from '../generated/index.gen'
3+
import { InputAction } from '../generated/pb/decentraland/sdk/components/common/input_action.gen'
4+
5+
const ROOT_ENTITY = 0 as Entity
6+
7+
// Every on-screen gamepad action button.
8+
const ALL_BUTTONS: InputAction[] = [
9+
InputAction.IA_POINTER,
10+
InputAction.IA_PRIMARY,
11+
InputAction.IA_SECONDARY,
12+
InputAction.IA_JUMP,
13+
InputAction.IA_ACTION_3,
14+
InputAction.IA_ACTION_4,
15+
InputAction.IA_ACTION_5,
16+
InputAction.IA_ACTION_6
17+
]
18+
19+
/**
20+
* @public
21+
* TouchScreenControls with convenience helpers. All helpers write the component onto the
22+
* RootEntity (where the client reads it) and merge with the current value.
23+
*/
24+
export interface TouchScreenControlsComponentDefinitionExtended
25+
extends LastWriteWinElementSetComponentDefinition<PBTouchScreenControls> {
26+
/** Hide every on-screen gamepad button. */
27+
hideAll(): void
28+
/**
29+
* Show every on-screen gamepad button (clears the button hide list).
30+
* Does NOT change the joystick/crosshair — use `showJoystick()` / `showCrosshair()` for those.
31+
*/
32+
showAll(): void
33+
/** Hide the given on-screen buttons (merged into the current config). */
34+
hide(actions: InputAction[]): void
35+
/** Set which action the large central button triggers. */
36+
setMainAction(action: InputAction): void
37+
/** Hide the native virtual joystick. */
38+
hideJoystick(): void
39+
/** Show the native virtual joystick. */
40+
showJoystick(): void
41+
/** Hide the on-screen crosshair / reticle. */
42+
hideCrosshair(): void
43+
/** Show the on-screen crosshair / reticle. */
44+
showCrosshair(): void
45+
}
46+
47+
export function defineTouchScreenControlsComponent(
48+
engine: Pick<IEngine, 'defineComponentFromSchema'>
49+
): TouchScreenControlsComponentDefinitionExtended {
50+
const theComponent = TouchScreenControls(engine)
51+
52+
// A mutable copy of the current RootEntity value (or a fresh default).
53+
function current(): PBTouchScreenControls {
54+
const value = theComponent.getOrNull(ROOT_ENTITY)
55+
return {
56+
touchInputs: value?.touchInputs
57+
? value.touchInputs.map((t) => ({ ...t, icon: t.icon ? { ...t.icon } : t.icon }))
58+
: [],
59+
mainAction: value?.mainAction,
60+
hideJoystick: value?.hideJoystick ?? false,
61+
hideCrosshair: value?.hideCrosshair ?? false
62+
}
63+
}
64+
65+
function setHidden(actions: InputAction[]): void {
66+
const value = current()
67+
const byAction = new Map<InputAction, PBTouchScreenControls_TouchInput>()
68+
for (const input of value.touchInputs) byAction.set(input.inputAction, input)
69+
for (const action of actions) byAction.set(action, { ...byAction.get(action), inputAction: action, hide: true })
70+
value.touchInputs = [...byAction.values()]
71+
theComponent.createOrReplace(ROOT_ENTITY, value)
72+
}
73+
74+
return {
75+
...theComponent,
76+
hideAll(): void {
77+
setHidden(ALL_BUTTONS)
78+
},
79+
showAll(): void {
80+
const value = current()
81+
value.touchInputs = []
82+
theComponent.createOrReplace(ROOT_ENTITY, value)
83+
},
84+
hide(actions: InputAction[]): void {
85+
setHidden(actions)
86+
},
87+
setMainAction(action: InputAction): void {
88+
const value = current()
89+
value.mainAction = action
90+
theComponent.createOrReplace(ROOT_ENTITY, value)
91+
},
92+
hideJoystick(): void {
93+
const value = current()
94+
value.hideJoystick = true
95+
theComponent.createOrReplace(ROOT_ENTITY, value)
96+
},
97+
showJoystick(): void {
98+
const value = current()
99+
value.hideJoystick = false
100+
theComponent.createOrReplace(ROOT_ENTITY, value)
101+
},
102+
hideCrosshair(): void {
103+
const value = current()
104+
value.hideCrosshair = true
105+
theComponent.createOrReplace(ROOT_ENTITY, value)
106+
},
107+
showCrosshair(): void {
108+
const value = current()
109+
value.hideCrosshair = false
110+
theComponent.createOrReplace(ROOT_ENTITY, value)
111+
}
112+
}
113+
}

packages/@dcl/ecs/src/components/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ import { defineInputModifierComponent, InputModifierComponentDefinitionExtended
2121
import { defineLightSourceComponent, LightSourceComponentDefinitionExtended } from './extended/LightSource'
2222
import { defineTriggerAreaComponent, TriggerAreaComponentDefinitionExtended } from './extended/TriggerArea'
2323
import { defineParticleSystemComponent, ParticleSystemComponentDefinitionExtended } from './extended/ParticleSystem'
24+
import {
25+
defineTouchScreenControlsComponent,
26+
TouchScreenControlsComponentDefinitionExtended
27+
} from './extended/TouchScreenControls'
2428
import defineTagsComponent, { TagsComponentDefinitionExtended } from './manual/Tags'
2529

2630
export * from './generated/index.gen'
@@ -87,6 +91,10 @@ export const TriggerArea: LwwComponentGetter<TriggerAreaComponentDefinitionExten
8791
export const ParticleSystem: LwwComponentGetter<ParticleSystemComponentDefinitionExtended> = (engine) =>
8892
defineParticleSystemComponent(engine)
8993

94+
/* @__PURE__ */
95+
export const TouchScreenControls: LwwComponentGetter<TouchScreenControlsComponentDefinitionExtended> = (engine) =>
96+
defineTouchScreenControlsComponent(engine)
97+
9098
/**
9199
* @alpha
92100
*/

packages/@dcl/ecs/src/components/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export type { INetowrkParent, INetowrkParentType } from './manual/NetworkParent'
2828
export type { InputModifierHelper, InputModifierComponentDefinitionExtended } from './extended/InputModifier'
2929
export type { LightSourceHelper, LightSourceComponentDefinitionExtended } from './extended/LightSource'
3030
export type { TriggerAreaComponentDefinitionExtended } from './extended/TriggerArea'
31+
export type { TouchScreenControlsComponentDefinitionExtended } from './extended/TouchScreenControls'
3132
export type {
3233
ParticleSystemHelper,
3334
ParticleSystemComponentDefinitionExtended,

packages/@dcl/ecs/src/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ import {
4141
InputModifierComponentDefinitionExtended,
4242
LightSourceComponentDefinitionExtended,
4343
TriggerAreaComponentDefinitionExtended,
44-
ParticleSystemComponentDefinitionExtended
44+
ParticleSystemComponentDefinitionExtended,
45+
TouchScreenControlsComponentDefinitionExtended
4546
} from './components/types'
4647
import { NameComponent } from './components/manual/Name'
4748
import { TagsComponentDefinitionExtended } from './components/manual/Tags'
@@ -64,6 +65,8 @@ export const LightSource: LightSourceComponentDefinitionExtended = /* @__PURE__*
6465
export const TriggerArea: TriggerAreaComponentDefinitionExtended = /* @__PURE__*/ components.TriggerArea(engine)
6566
export const ParticleSystem: ParticleSystemComponentDefinitionExtended =
6667
/* @__PURE__*/ components.ParticleSystem(engine)
68+
export const TouchScreenControls: TouchScreenControlsComponentDefinitionExtended =
69+
/* @__PURE__*/ components.TouchScreenControls(engine)
6770

6871
/**
6972
* @alpha

packages/@dcl/playground-assets/etc/playground-assets.api.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1187,6 +1187,7 @@ export type EntityComponents = {
11871187
uiBackground: PBUiBackground;
11881188
uiInput: PBUiInput;
11891189
uiDropdown: PBUiDropdown;
1190+
uiInputBinding: PBUiInputBinding;
11901191
onMouseDown: Callback;
11911192
onMouseUp: Callback;
11921193
onMouseEnter: Callback;
@@ -1207,6 +1208,7 @@ export enum EntityMappingMode {
12071208
export interface EntityPropTypes extends Listeners {
12081209
key?: Key;
12091210
uiBackground?: UiBackgroundProps;
1211+
uiInputBinding?: PBUiInputBinding;
12101212
uiTransform?: UiTransformProps;
12111213
}
12121214

@@ -5103,8 +5105,22 @@ export const ToLinearSpace = 2.2;
51035105
// @public (undocumented)
51045106
export type ToOptional<T> = OnlyOptionalUndefinedTypes<T> & OnlyNonUndefinedTypes<T>;
51055107

5108+
// Warning: (ae-missing-release-tag) "TouchScreenControls" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
5109+
//
51065110
// @public (undocumented)
5107-
export const TouchScreenControls: LastWriteWinElementSetComponentDefinition<PBTouchScreenControls>;
5111+
export const TouchScreenControls: TouchScreenControlsComponentDefinitionExtended;
5112+
5113+
// @public
5114+
export interface TouchScreenControlsComponentDefinitionExtended extends LastWriteWinElementSetComponentDefinition<PBTouchScreenControls> {
5115+
hide(actions: InputAction[]): void;
5116+
hideAll(): void;
5117+
hideCrosshair(): void;
5118+
hideJoystick(): void;
5119+
setMainAction(action: InputAction): void;
5120+
showAll(): void;
5121+
showCrosshair(): void;
5122+
showJoystick(): void;
5123+
}
51085124

51095125
// Warning: (ae-missing-release-tag) "Transform" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
51105126
//

packages/@dcl/react-ecs/src/components/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { PBUiInputBinding } from '@dcl/ecs'
12
import { ReactEcs } from '../react-ecs'
23
import { Listeners } from './listeners/types'
34
import { UiBackgroundProps } from './uiBackground/types'
@@ -12,6 +13,8 @@ export interface EntityPropTypes extends Listeners {
1213
uiTransform?: UiTransformProps
1314
/** To define a background color or image */
1415
uiBackground?: UiBackgroundProps
16+
/** Bind input actions to this element, held down while it's pressed (touch or pointer) */
17+
uiInputBinding?: PBUiInputBinding
1518
/** Uinique key to identiy elments when iterating arrays */
1619
key?: Key
1720
}

packages/@dcl/react-ecs/src/react-ecs.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* eslint-disable @typescript-eslint/no-empty-interface */
2-
import { PBUiBackground, PBUiText, PBUiTransform, PBUiInput, PBUiDropdown } from '@dcl/ecs'
2+
import { PBUiBackground, PBUiText, PBUiTransform, PBUiInput, PBUiDropdown, PBUiInputBinding } from '@dcl/ecs'
33
import React from 'react'
44
import { Callback, Key } from './components'
55

@@ -19,6 +19,7 @@ export type EntityComponents = {
1919
uiBackground: PBUiBackground
2020
uiInput: PBUiInput
2121
uiDropdown: PBUiDropdown
22+
uiInputBinding: PBUiInputBinding
2223
onMouseDown: Callback
2324
onMouseUp: Callback
2425
onMouseEnter: Callback

packages/@dcl/react-ecs/src/reconciler/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export function createReconciler(
7171
const UiInputResult = components.UiInputResult(engine)
7272
const UiDropdown = components.UiDropdown(engine)
7373
const UiDropdownResult = components.UiDropdownResult(engine)
74+
const UiInputBinding = components.UiInputBinding(engine)
7475

7576
// Component ID Helper
7677
const getComponentId: {
@@ -80,7 +81,8 @@ export function createReconciler(
8081
uiText: UiText.componentId,
8182
uiBackground: UiBackground.componentId,
8283
uiInput: UiInput.componentId,
83-
uiDropdown: UiDropdown.componentId
84+
uiDropdown: UiDropdown.componentId,
85+
uiInputBinding: UiInputBinding.componentId
8486
}
8587

8688
function pointerEventCallback(entity: Entity, pointerEvent: PointerEventType) {

packages/@dcl/react-ecs/src/reconciler/utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ const entityComponent: EntityComponents = {
7474
onMouseEnter: undefined as any,
7575
onMouseLeave: undefined as any,
7676
uiInput: undefined as any,
77-
uiDropdown: undefined as any
77+
uiDropdown: undefined as any,
78+
uiInputBinding: undefined as any
7879
}
7980
export const componentKeys: (keyof EntityComponents)[] = Object.keys(entityComponent) as (keyof EntityComponents)[]
8081

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { Engine, Entity, components, InputAction } from '../../../packages/@dcl/ecs/src'
2+
import { testComponentSerialization } from './assertion'
3+
4+
const ROOT_ENTITY = 0 as Entity
5+
6+
describe('TouchScreenControls convenience helpers', () => {
7+
it('hideAll() hides every gamepad button', () => {
8+
const engine = Engine()
9+
const TouchScreenControls = components.TouchScreenControls(engine)
10+
11+
TouchScreenControls.hideAll()
12+
13+
const value = TouchScreenControls.getOrNull(ROOT_ENTITY)
14+
expect(value?.touchInputs.length).toBe(8)
15+
expect(value?.touchInputs.every((t) => t.hide)).toBe(true)
16+
})
17+
18+
it('showAll() clears the button hide list but leaves joystick/crosshair untouched', () => {
19+
const engine = Engine()
20+
const TouchScreenControls = components.TouchScreenControls(engine)
21+
22+
TouchScreenControls.hideJoystick()
23+
TouchScreenControls.hideCrosshair()
24+
TouchScreenControls.hideAll()
25+
TouchScreenControls.showAll()
26+
27+
const value = TouchScreenControls.getOrNull(ROOT_ENTITY)
28+
expect(value?.touchInputs).toEqual([])
29+
// showAll only affects buttons — joystick/crosshair stay hidden
30+
expect(value?.hideJoystick).toBe(true)
31+
expect(value?.hideCrosshair).toBe(true)
32+
})
33+
34+
it('hide() merges the given actions into the current config', () => {
35+
const engine = Engine()
36+
const TouchScreenControls = components.TouchScreenControls(engine)
37+
38+
TouchScreenControls.hide([InputAction.IA_JUMP])
39+
TouchScreenControls.hide([InputAction.IA_PRIMARY])
40+
41+
const value = TouchScreenControls.getOrNull(ROOT_ENTITY)
42+
const hidden = value?.touchInputs.map((t) => t.inputAction) ?? []
43+
expect(hidden).toContain(InputAction.IA_JUMP)
44+
expect(hidden).toContain(InputAction.IA_PRIMARY)
45+
})
46+
47+
it('hide() preserves a previously configured icon (no data loss)', () => {
48+
const engine = Engine()
49+
const TouchScreenControls = components.TouchScreenControls(engine)
50+
51+
const icon = { tex: { $case: 'texture' as const, texture: { src: 'custom-jump' } } }
52+
TouchScreenControls.createOrReplace(ROOT_ENTITY, {
53+
touchInputs: [{ inputAction: InputAction.IA_JUMP, hide: false, icon }],
54+
hideJoystick: false,
55+
hideCrosshair: false
56+
})
57+
58+
TouchScreenControls.hide([InputAction.IA_JUMP])
59+
60+
const entry = TouchScreenControls.getOrNull(ROOT_ENTITY)?.touchInputs.find(
61+
(t) => t.inputAction === InputAction.IA_JUMP
62+
)
63+
expect(entry?.hide).toBe(true)
64+
expect(entry?.icon).toEqual(icon)
65+
})
66+
67+
it('setMainAction() sets the central button action', () => {
68+
const engine = Engine()
69+
const TouchScreenControls = components.TouchScreenControls(engine)
70+
71+
TouchScreenControls.setMainAction(InputAction.IA_PRIMARY)
72+
73+
expect(TouchScreenControls.getOrNull(ROOT_ENTITY)?.mainAction).toBe(InputAction.IA_PRIMARY)
74+
})
75+
76+
it('hideJoystick()/showJoystick() and hideCrosshair()/showCrosshair() toggle visibility', () => {
77+
const engine = Engine()
78+
const TouchScreenControls = components.TouchScreenControls(engine)
79+
80+
TouchScreenControls.hideJoystick()
81+
TouchScreenControls.hideCrosshair()
82+
expect(TouchScreenControls.getOrNull(ROOT_ENTITY)?.hideJoystick).toBe(true)
83+
expect(TouchScreenControls.getOrNull(ROOT_ENTITY)?.hideCrosshair).toBe(true)
84+
85+
TouchScreenControls.showJoystick()
86+
TouchScreenControls.showCrosshair()
87+
expect(TouchScreenControls.getOrNull(ROOT_ENTITY)?.hideJoystick).toBe(false)
88+
expect(TouchScreenControls.getOrNull(ROOT_ENTITY)?.hideCrosshair).toBe(false)
89+
})
90+
})
91+
92+
describe('Generated TouchScreenControls ProtoBuf', () => {
93+
it('should serialize/deserialize TouchScreenControls', () => {
94+
const newEngine = Engine()
95+
const TouchScreenControls = components.TouchScreenControls(newEngine)
96+
97+
testComponentSerialization(TouchScreenControls, {
98+
touchInputs: [
99+
{ inputAction: InputAction.IA_SECONDARY, hide: true, icon: undefined },
100+
{
101+
inputAction: InputAction.IA_JUMP,
102+
hide: false,
103+
icon: {
104+
tex: {
105+
$case: 'texture',
106+
texture: {
107+
src: 'custom-jump',
108+
wrapMode: undefined,
109+
filterMode: undefined,
110+
offset: undefined,
111+
tiling: undefined
112+
}
113+
}
114+
}
115+
}
116+
],
117+
mainAction: InputAction.IA_PRIMARY,
118+
hideJoystick: true,
119+
hideCrosshair: true
120+
})
121+
122+
testComponentSerialization(TouchScreenControls, {
123+
touchInputs: [],
124+
mainAction: undefined,
125+
hideJoystick: false,
126+
hideCrosshair: false
127+
})
128+
})
129+
})

0 commit comments

Comments
 (0)