import { ChangeDetectionStrategy, Component, computed, input, output } from '@angular/core'; import { ScaleBar } from '../../shared/format/scale-bar'; import { ViewLevel } from '../../shared/state/navigation.store'; import { ReticleIconComponent } from '../../shared/ui/reticle-icon.component'; interface LadderStep { readonly level: ViewLevel; readonly label: string; /** Above the active step, on it, or below it — which is what the step is styled from. */ readonly state: 'above' | 'current' | 'below'; /** Whether this step is somewhere the view can be sent right now. */ readonly reachable: boolean; } /** Outermost first — the order the ladder is drawn in, and the order levels nest. */ const LADDER: readonly { level: ViewLevel; label: string }[] = [ { level: 'galactic', label: 'Milky Way' }, { level: 'galaxy', label: 'Solar Neighbourhood' }, { level: 'system', label: 'System' } ]; /** * The top of the map's heads-up display: the scale ladder on the left with the scale bar under * it, the nameplate across the centre, and a centre reticle on whatever the camera is holding. * Readouts and tools live in the dock along the bottom (`HudDockComponent`). * * Purely presentational — every value arrives as an input and the only thing it emits is a * request to move to another scale. The scene owns the camera and decides what that means. * * Reachable levels render as buttons and the current one renders as a static marker rather than * a button that does nothing. The galactic and neighbourhood scales are always reachable, in * both directions: they are one continuous space and the Sun is always in it. The system scale * is not, since there is no system to go to until a star has been picked. */ @Component({ selector: 'app-starmap-hud', changeDetection: ChangeDetectionStrategy.OnPush, imports: [ReticleIconComponent], host: { class: 'pointer-events-none absolute inset-0 block select-none' }, template: ` @if (showReticle()) { } @if (scale(); as bar) { } @if (title()) { } ` }) export class StarmapHudComponent { readonly level = input.required(); /** What the view is holding, for the nameplate — the selected star, or nothing. */ readonly title = input(''); readonly showReticle = input(true); /** The scale bar for the current zoom, worked out by the scene, which knows the camera. */ readonly scale = input(null); readonly levelSelected = output(); readonly ladder = computed(() => { const activeIndex = LADDER.findIndex((step) => step.level === this.level()); return LADDER.map((step, index) => ({ ...step, state: index < activeIndex ? 'above' : index === activeIndex ? 'current' : 'below', // Every scale is somewhere the camera can be sent except the current one and the system // level, which needs a star picked first — there is no "the system" without a selection. reachable: index !== activeIndex && step.level !== 'system' })); }); }