diff --git a/apps/scatter/src/game/stateLayout.ts b/apps/scatter/src/game/stateLayout.ts index bd94c7eac..2293b97b7 100644 --- a/apps/scatter/src/game/stateLayout.ts +++ b/apps/scatter/src/game/stateLayout.ts @@ -11,4 +11,11 @@ export const { stateLayout, stateLayoutDerived } = createLayout({ landscape: { width: 1600, height: 900 }, portrait: { width: 800, height: 1422 }, }, + // height the UI bar takes along the bottom, in mainSizesMap units + safeAreaBottomMap: { + desktop: 220, + tablet: 260, + landscape: 240, + portrait: 380, + }, }); diff --git a/packages/utils-layout/src/createLayout.svelte.ts b/packages/utils-layout/src/createLayout.svelte.ts index 8706e3035..a39aca903 100644 --- a/packages/utils-layout/src/createLayout.svelte.ts +++ b/packages/utils-layout/src/createLayout.svelte.ts @@ -31,6 +31,14 @@ export const createLayout = (layoutOptions: { portrait: number; }; mainSizesMap: MainSizesMap; + /** + * Height the UI chrome occupies along the bottom, per layout type, in the same + * units as `mainSizesMap`. Reserving it keeps the game area clear of the UI bar + * when the canvas is wider than the design ratio - without it the game area + * takes the full canvas height and the bottom of the board renders behind the + * bar. Omit it and the layout is unchanged. + */ + safeAreaBottomMap?: Partial>; }) => { const canvasSizes = () => ({ width: innerWidth.current ?? 1, height: innerHeight.current ?? 1 }); // because of resizeTo: window const canvasRatio = () => getRatio(canvasSizes()); @@ -56,25 +64,32 @@ export const createLayout = (layoutOptions: { }; const isStacked = () => ['portrait', 'almostSquare'].includes(layoutType()); - const createMainLayout = (mainSizesMap: MainSizesMap) => () => { - const x = canvasSizes().width * 0.5; - const y = canvasSizes().height * 0.5; - const mainSizes = mainSizesMap[layoutType()]; - const widthScale = canvasSizes().width / mainSizes.width; - const heightScale = canvasSizes().height / mainSizes.height; - const scale = Math.min(widthScale, heightScale); + const createMainLayout = + ( + mainSizesMap: MainSizesMap, + safeAreaBottomMap?: Partial>, + ) => + () => { + const mainSizes = mainSizesMap[layoutType()]; + const safeAreaBottom = safeAreaBottomMap?.[layoutType()] ?? 0; + const widthScale = canvasSizes().width / mainSizes.width; + const heightScale = canvasSizes().height / (mainSizes.height + safeAreaBottom); + const scale = Math.min(widthScale, heightScale); + const x = canvasSizes().width * 0.5; + // centre in the area above the reserved strip, not in the whole canvas + const y = canvasSizes().height * 0.5 - safeAreaBottom * scale * 0.5; - return { - x, - y, - scale, - width: mainSizes.width, - height: mainSizes.height, - anchor: 0.5, + return { + x, + y, + scale, + width: mainSizes.width, + height: mainSizes.height, + anchor: 0.5, + }; }; - }; - const mainLayout = createMainLayout(layoutOptions.mainSizesMap); + const mainLayout = createMainLayout(layoutOptions.mainSizesMap, layoutOptions.safeAreaBottomMap); const mainLayoutStandard = createMainLayout(STANDARD_MAIN_SIZES_MAP);