UNPKG

one

Version:

One is a React framework for web, iOS, and Android apps.

99 lines (90 loc) • 3.84 kB
import { createContext, useContext, useMemo } from 'react' import type { ReservedRegion, ReservedRegionOptions, WindowSegment } from './types' // shared by the native and web providers: the nearest provider's latest // reading and whether it has taken one yet. export interface ReservedRegionsSnapshot { regions: readonly ReservedRegion[] ready: boolean bounds: { width: number; height: number } | null } export const ReservedRegionsContext = createContext<ReservedRegionsSnapshot | null>(null) function useSnapshot(hook: string): ReservedRegionsSnapshot { const snapshot = useContext(ReservedRegionsContext) if (!snapshot) { throw new Error(`One.${hook} must be used inside One.UI.ReservedRegions.Provider`) } return snapshot } /** * The regions reserved inside the nearest ReservedRegions.Provider, in its * coordinates: a `division` (a fold content should not straddle) or an * `occlusion` (a camera or system control hiding content). Active regions * only unless `includeInactive` is set. */ export function useRegions(options?: ReservedRegionOptions): readonly ReservedRegion[] { const { regions } = useSnapshot('useReservedRegions') const kind = options?.kind const includeInactive = options?.includeInactive === true return useMemo( () => regions.filter( (region) => (!kind || region.kind === kind) && (includeInactive || region.isActive) ), [regions, kind, includeInactive] ) } /** * Whether the nearest provider has taken its first reading, an empty one * included. It stays true for the provider's lifetime. */ export function useReady(): boolean { return useSnapshot('useReservedRegionsReady').ready } /** * parts of the provider separated by an active full-span division. Put the * provider around the full window to read window segments. Until its native * reading arrives, the result is empty. An unspanned window has one * segment. Occlusions do not divide a window. */ export function useSegments(): readonly WindowSegment[] { const { bounds, ready, regions } = useSnapshot('useWindowSegments') return useMemo(() => { if (!ready || !bounds || bounds.width <= 0 || bounds.height <= 0) return [] return segmentsFor(bounds, regions) }, [bounds, ready, regions]) } /** whether an active division spans the provider. */ export function useSpanning(): boolean { return useSegments().length > 1 } export function segmentsFor( bounds: { width: number; height: number }, regions: readonly ReservedRegion[], ): WindowSegment[] { // android converts each pixel edge to a float DIP independently. Allow a // subpoint rounding difference when testing whether a fold crosses an edge. const edgeTolerance = 0.5 let segments: WindowSegment[] = [{ x: 0, y: 0, ...bounds }] for (const region of regions) { if (region.kind !== 'division' || !region.isActive) continue const { x, y, width, height } = region.frame segments = segments.flatMap((segment) => { const right = segment.x + segment.width const bottom = segment.y + segment.height if (y <= segment.y + edgeTolerance && y + height >= bottom - edgeTolerance && x > segment.x && x + width < right) { return [ { x: segment.x, y: segment.y, width: x - segment.x, height: segment.height }, { x: x + width, y: segment.y, width: right - x - width, height: segment.height }, ] } if (x <= segment.x + edgeTolerance && x + width >= right - edgeTolerance && y > segment.y && y + height < bottom) { return [ { x: segment.x, y: segment.y, width: segment.width, height: y - segment.y }, { x: segment.x, y: y + height, width: segment.width, height: bottom - y - height }, ] } return [segment] }) } return segments.sort((a, b) => a.y - b.y || a.x - b.x) }