Scrollable UI
This experimental feature enables you to add scrollbars to UI in case of overflow the container size.
What it changes
This proposal changed the UiTransform
and add a new component UiScrollResult
.
UiTransform
Difference between experimental version and original one
+/**
+ * @public
+ * The scroll-visible determines if the scrollbars are shown when the scroll overflow is enabled
+ */
+export type ScrollVisibleType = 'horizontal' | 'vertical' | 'both' | 'hidden'
export interface UiTransformProps {
...,
+ /** A reference value to identify the element, default empty */
+ elementId?: string
+ /** default position=(0,0) if it aplies, a vector or a reference-id */
+ scrollPosition?: Vector2 | string
+ /** default ShowScrollBar.SSB_BOTH */
+ scrollVisible?: ScrollVisibleType
}
Summary:
- It adds
elementId
to identify each UI element, it's a unique identifier. You can use it to identify outside the UI system, or to reference it in thescrollPosition
- It adds
scrollPosition
to manually set the current scroll position. It can be a normalized 2D vector (0-1, 0-1) or a string to reference another UI element. - It adds
scrollVisible
to set the bars visibility
UiScrollResult
+export interface PBUiScrollResult {
+ value: PBVector2 | undefined;
+}
The value indicates how scrolled is the container where 0.0
is at the beginning and 1.0
full scrolled. The x-value is for horizontal axes and y-value for the vertical one.
Examples
Copy one of the following code to the index.ts and then run the Bevy Explorer preview with npm start
.
Scrollable UI with a Scroll Container


import ReactEcs, { Label, UiEntity } from '@dcl/react-ecs'
import { UiScrollResult, UiTransform, engine } from '@dcl/sdk/ecs'
import { Color4 } from '@dcl/sdk/math'
import { Button, Input, ReactEcsRenderer } from '@dcl/sdk/react-ecs'
import { type Vector2 } from '~system/EngineApi'
class UiExample {
// autoincrement counter, only for demonstration purposes
private counter: number = 0
// target for scroll position
private target: string | Vector2 = { x: 0.5, y: 0.5 }
// text to display in the scroll controller
private scrollText = 'indeterminated'
// id of the scroll container, to identify it in the controller
private readonly scrollContainerId = 'my-scroll-container-A'
constructor() {
engine.addSystem(this.controllerSystem.bind(this))
}
controllerSystem(): void {
for (const [, pos, uiTransform] of engine.getEntitiesWith(
UiScrollResult,
UiTransform
)) {
if (uiTransform.elementId !== this.scrollContainerId) {
continue
}
if (pos.value === undefined) {
break
}
if (pos.value.y <= 0) {
this.scrollText = 'top'
} else if (pos.value.y >= 1) {
this.scrollText = 'bottom'
} else if (pos.value.y < 0.5) {
this.scrollText = 'near top'
} else {
this.scrollText = 'near bottom'
}
if (pos.value.x <= 0) {
this.scrollText += ' left'
} else if (pos.value.x >= 1) {
this.scrollText += ' right'
} else {
this.scrollText += ' middle'
}
}
this.counter++
}
// This UI is only for demonstration purposes, not the focus of this example
ScrollController(): ReactEcs.JSX.Element {
return (
<UiEntity
uiTransform={{
flexDirection: 'column',
position: { left: '25%', top: '10%' },
width: '200',
height: '300',
justifyContent: 'space-evenly',
alignItems: 'center'
}}
uiBackground={{ color: Color4.create(0.0, 0.0, 0.0, 1.0) }}
>
<Label value="Scroll controller" color={Color4.Green()} fontSize={14} />
<Button
fontSize={16}
uiTransform={{ width: '80%' }}
value="Focus first item"
onMouseDown={() => {
this.target = 'first'
}}
/>
<Button
fontSize={16}
uiTransform={{ width: '80%' }}
value="Focus second item"
onMouseDown={() => {
this.target = 'second'
}}
/>
<Label
fontSize={16}
value={`Currently:\n${this.scrollText}`}
color={Color4.White()}
/>
<Input
fontSize={16}
uiTransform={{ width: '90%' }}
placeholder="type target"
onChange={(value) => {
console.log(`change ${value}`)
this.target = value
}}
onSubmit={(value) => {
console.log(`submit ${value}`)
this.target = value
}}
/>
</UiEntity>
)
}
Scrolly(): ReactEcs.JSX.Element {
return (
<UiEntity
uiTransform={{
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'space-between',
positionType: 'absolute',
width: '400',
height: '600',
position: { right: '8%', bottom: '3%' },
// new properties
overflow: 'scroll', // enable scrolling
scrollPosition: this.target, // if you want to set the scroll position programatically (maybe an action from the user)
elementId: this.scrollContainerId // id to identify the scroll result if you need to
}}
uiBackground={{
color: Color4.White()
}}
>
<Label
uiTransform={{
height: 'auto',
width: 'auto',
margin: '200px',
padding: `10px`,
// new property: we set the id, it must be unique, and we will use it to identify the scroll position
elementId: 'first'
}}
value={`first (${this.counter})`}
color={Color4.Black()}
fontSize={18}
textAlign="middle-center"
key="first"
/>
<Label
uiTransform={{
height: 'auto',
width: 'auto',
margin: '200px',
padding: `10px`,
// new property: we set the id, it must be unique, and we will use it to identify the scroll position
elementId: 'second'
}}
value="second"
color={Color4.Black()}
fontSize={18}
textAlign="middle-center"
/>
<Label
uiTransform={{
height: 'auto',
width: 'auto',
margin: '200px',
padding: `10px`,
// new property: we set the id, it must be unique, and we will use it to identify the scroll position
elementId: 'third'
}}
value="third"
color={Color4.Black()}
fontSize={18}
textAlign="middle-center"
/>
<Label
uiTransform={{
height: 'auto',
width: 'auto',
margin: '200px',
padding: `10px`,
// new property: we set the id, it must be unique, and we will use it to identify the scroll position
elementId: 'fourth'
}}
value="fourth"
color={Color4.Black()}
fontSize={18}
textAlign="middle-center"
/>
<Label
uiTransform={{
height: 'auto',
width: 'auto',
margin: '200px',
padding: `10px`,
// new property: we set the id, it must be unique, and we will use it to identify the scroll position
elementId: 'fifth'
}}
value="fifth"
color={Color4.Black()}
fontSize={18}
textAlign="middle-center"
/>
</UiEntity>
)
}
render(): ReactEcs.JSX.Element[] {
return [this.Scrolly(), this.ScrollController()]
}
}
export function main(): void {
const ui = new UiExample()
ReactEcsRenderer.setUiRenderer(ui.render.bind(ui))
}