Click the dropdown to open it, then pick an option.
const renderLayers = {
world: 1 << 0,
ui: 1 << 1,
};
const textColor = new Color(0.12, 0.12, 0.16, 1);
async function createBackdrop(
world: EcsWorld,
renderContext: RenderContext,
): Promise<void> {
const whiteImage = await renderContext.imageCache.getOrLoad(
'img/White.png',
);
const backdropSprite = createImageSprite(whiteImage, renderContext, {
pixelsPerUnit: 1,
layer: renderLayers.world,
});
backdropSprite.tintColor = new Color(0.09, 0.11, 0.16, 1);
const { x: width, y: height } = calculateVisibleWorldSize(
renderContext.width,
renderContext.height,
DEMO_VERTICAL_WORLD_UNITS,
);
backdropSprite.width = width;
backdropSprite.height = height;
const backdrop = world.createEntity();
addPositionComponent(world, backdrop);
addSpriteComponent(world, backdrop, backdropSprite);
}
function createPointerInput(
world: EcsWorld,
time: Time,
game: Game,
): MouseInputSource {
const inputManager = registerInputs(world, time, {});
return new MouseInputSource(inputManager, game.container);
}
/**
* Builds the dropdown demo: a single `createDropdown` showing the selected
* option in its header, with a click-to-open list of option rows below it -
* each an ordinary `createButton`. Selecting an option updates the header's
* label, raises `onValueChanged`, and closes the list.
* @param fontAtlasUrl - The URL of the font atlas JSON to load.
* @returns The created game.
*/
export const createDropdownGame = async (
fontAtlasUrl: string,
): Promise<Game> => {
const { game, world, renderContext, time } = createGame('demo-game');
createCamera(world, {
isStatic: true,
cullingMask: renderLayers.world,
verticalWorldUnits: DEMO_VERTICAL_WORLD_UNITS,
});
await createBackdrop(world, renderContext);
const fontAtlasCache = new FontAtlasCache(renderContext.imageCache);
const fontAtlas: FontAtlas = await fontAtlasCache.getOrLoad(fontAtlasUrl);
const mouseInputSource = createPointerInput(world, time, game);
registerUiSystems(world, renderContext, time, {
pointerSource: mouseInputSource,
});
const canvas = createUiCanvas(world, renderContext, {
cullingMask: renderLayers.ui,
referenceResolution: { x: 1920, y: 1080 },
});
const whiteImage = await renderContext.imageCache.getOrLoad(
'img/White.png',
);
const boxColor = new Color(0.85, 0.85, 0.88, 1);
const boxSprite = createImageSprite(whiteImage, renderContext, {
pixelsPerUnit: 1,
layer: renderLayers.ui,
});
boxSprite.tintColor = boxColor;
const boxTransition = {
normalColor: boxColor,
hoverColor: new Color(0.78, 0.78, 0.83, 1),
pressedColor: new Color(0.68, 0.68, 0.75, 1),
disabledColor: new Color(0.6, 0.6, 0.6, 0.6),
};
createDropdown(world, canvas, {
headerSprite: boxSprite,
optionSprite: boxSprite,
options: ['Low', 'Medium', 'High', 'Ultra'],
fontAtlas,
labelColor: textColor,
labelCategory: renderLayers.ui,
anchor: UiAnchor.center({ x: 320, y: 56 }),
anchoredPosition: { x: 0, y: 100 },
selectedIndex: 2,
transition: boxTransition,
});
world.addSystem(createCameraEcsSystem(time));
world.addSystem(createTransformEcsSystem());
world.addSystem(createTextShapingEcsSystem(renderContext));
world.addSystem(createRenderEcsSystem(renderContext));
world.addSystem(createPresentEcsSystem(renderContext));
return game;
};
createDropdown builds a header (an ordinary createButton showing the currently-selected option) with a UiDropdownEcsComponent, plus one option-row button per entry - stacked below the header and hidden until it's clicked open. A chevron on the header's right edge flips between v (closed) and ^ (open) as the list toggles. Selecting an option updates the header's label, raises onValueChanged, and closes the list. Clicking outside the open list doesn't close it - only clicking the header again or selecting an option does.