1681-engine 0.5.8 → 0.5.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +53 -0
- package/dist/1681-engine.js +463 -409
- package/dist/ambient/ColorGrade.js +0 -1
- package/dist/ambient/DayClock.js +0 -1
- package/dist/ambient/WeatherLayer.js +0 -1
- package/dist/ambient/grade.js +0 -1
- package/dist/ambient/weather.js +0 -1
- package/dist/anim/AnimStateMachine.js +0 -1
- package/dist/anim/Animator.js +0 -1
- package/dist/anim/animStateCompile.js +0 -1
- package/dist/anim/animStateSystem.js +0 -1
- package/dist/anim/animationSystem.js +0 -1
- package/dist/anim/asepriteSlices.js +0 -1
- package/dist/anim/asepriteTags.js +0 -1
- package/dist/anim/parseAseprite.js +0 -1
- package/dist/assets/AssetLoader.js +112 -14
- package/dist/assets/loadAseprite.js +0 -1
- package/dist/assets/takeCleanerWorker-DTcI_Jqr.js +0 -1
- package/dist/assets/textureAtlas.js +58 -0
- package/dist/audio/Footsteps.js +0 -1
- package/dist/audio/MusicPlayer.js +0 -1
- package/dist/audio/SoundEngine.js +13 -7
- package/dist/audio/cleanTake.js +0 -1
- package/dist/audio/music/ElementVoice.js +0 -1
- package/dist/audio/music/GraphVoice.js +0 -1
- package/dist/audio/music/MusicChannel.js +0 -1
- package/dist/audio/music/musicVoice.js +0 -1
- package/dist/audio/sampleBank.js +0 -1
- package/dist/audio/soundTable.js +0 -1
- package/dist/audio/synth.js +0 -1
- package/dist/core/BitmapLabel.js +0 -1
- package/dist/core/Camera.js +1 -2
- package/dist/core/Clock.js +31 -21
- package/dist/core/Graphics.js +0 -1
- package/dist/core/Group.js +0 -1
- package/dist/core/Image.js +0 -1
- package/dist/core/ObjectFactory.js +0 -1
- package/dist/core/Pointer.js +46 -24
- package/dist/core/Rectangle.js +0 -1
- package/dist/core/Stage.js +5 -6
- package/dist/core/Text.js +0 -1
- package/dist/core/TextureRegistry.js +54 -24
- package/dist/core/Timeline.js +0 -1
- package/dist/core/TimelineBuilder.js +0 -1
- package/dist/core/TimelineChild.js +0 -1
- package/dist/core/Tweener.js +0 -1
- package/dist/core/atlas.js +0 -1
- package/dist/core/atlasPlan.js +0 -1
- package/dist/core/blendLayer.js +11 -0
- package/dist/core/eases.js +0 -1
- package/dist/core/gameObject.js +22 -20
- package/dist/core/pixelZoom.js +43 -0
- package/dist/core/pixiPatches.js +0 -1
- package/dist/core/runDue.js +28 -0
- package/dist/core/silhouette.js +49 -0
- package/dist/core/timelinePlace.js +0 -1
- package/dist/core/tweenProps.js +0 -1
- package/dist/debug/DebugOverlay.js +0 -1
- package/dist/debug/DrawCallCounter.js +15 -12
- package/dist/debug/ErrorLog.js +0 -1
- package/dist/debug/FrameStats.js +0 -1
- package/dist/debug/inspectWorld.js +0 -1
- package/dist/diagnostics/ErrorDeduper.js +0 -1
- package/dist/diagnostics/FrameSampler.js +0 -1
- package/dist/diagnostics/bootWatch.js +0 -1
- package/dist/diagnostics/captureErrors.js +0 -1
- package/dist/diagnostics/contextLoss.js +0 -1
- package/dist/diagnostics/frameStats.js +0 -1
- package/dist/diagnostics/gpuInfo.js +0 -1
- package/dist/diagnostics/renderGuard.js +0 -1
- package/dist/ecs/SparseSet.js +0 -1
- package/dist/ecs/World.js +0 -1
- package/dist/ecs/fields.js +0 -1
- package/dist/engine/Engine.js +42 -18
- package/dist/engine/FixedLoop.js +0 -1
- package/dist/engine/HiddenPause.js +27 -0
- package/dist/engine/Scene.js +0 -1
- package/dist/engine/SceneManager.js +0 -1
- package/dist/events/EventBus.js +0 -1
- package/dist/fx/Afterimages.js +82 -0
- package/dist/fx/FloatingText.js +0 -1
- package/dist/fx/hitStop.js +0 -1
- package/dist/fx/viewCull.js +0 -1
- package/dist/i18n/Translator.js +0 -1
- package/dist/i18n/extractStrings.js +0 -1
- package/dist/i18n/formatMessage.js +0 -1
- package/dist/input/Actions.js +29 -21
- package/dist/input/AimGesture.js +0 -1
- package/dist/input/Keyboard.js +0 -1
- package/dist/input/Keymap.js +0 -1
- package/dist/input/TargetLock.js +0 -1
- package/dist/input/aimAxis.js +0 -1
- package/dist/input/keyChord.js +0 -1
- package/dist/input/longPress.js +0 -1
- package/dist/input/rumble.js +42 -0
- package/dist/input/stickAxis.js +0 -1
- package/dist/input/touchDetect.js +0 -1
- package/dist/light/LightLayer.js +0 -1
- package/dist/nav/MinHeap.js +0 -1
- package/dist/nav/findPath.js +0 -1
- package/dist/nav/navGrid.js +0 -1
- package/dist/nav/nearestOpen.js +0 -1
- package/dist/nav/straightenPath.js +0 -1
- package/dist/net/Connection.js +39 -16
- package/dist/net/InterpolationBuffer.js +0 -1
- package/dist/net/PredictedMover.js +0 -1
- package/dist/net/Room.js +0 -1
- package/dist/net/RoomHost.js +0 -1
- package/dist/net/cadence.js +0 -1
- package/dist/net/constants.js +0 -1
- package/dist/net/lobby/LobbyClient.js +0 -1
- package/dist/net/lobby/LobbyHost.js +0 -1
- package/dist/net/lobby/LobbyRooms.js +0 -1
- package/dist/net/lobby/Matchmaker.js +0 -1
- package/dist/net/movement.js +40 -39
- package/dist/net/protocol.js +0 -1
- package/dist/net/reconnect.js +10 -0
- package/dist/net/serverClock.js +0 -1
- package/dist/net/snapshotCodec.js +0 -1
- package/dist/parallax/Parallax.js +0 -1
- package/dist/parallax/ParallaxLayer.js +0 -1
- package/dist/parallax/parallaxMath.js +0 -1
- package/dist/particles/Emitter.js +0 -1
- package/dist/particles/ParticleBudget.js +0 -1
- package/dist/particles/ParticleSim.js +0 -1
- package/dist/particles/particleCurves.js +0 -1
- package/dist/particles/particleSampling.js +0 -1
- package/dist/physics/Body.js +0 -1
- package/dist/physics/BroadPhase.js +0 -1
- package/dist/physics/Contact.js +0 -1
- package/dist/physics/ContactSolver.js +0 -1
- package/dist/physics/PhysicsWorld.js +0 -1
- package/dist/physics/SpatialHash.js +0 -1
- package/dist/physics/collide.js +0 -1
- package/dist/physics/collidePolygon.js +0 -1
- package/dist/physics/contactReport.js +0 -1
- package/dist/physics/drawColliders.js +0 -1
- package/dist/physics/physicsSystem.js +0 -1
- package/dist/physics/polygon.js +0 -1
- package/dist/physics/raycast.js +0 -1
- package/dist/physics/syncToBody.js +0 -1
- package/dist/physics/tileBodies.js +0 -1
- package/dist/physics/types.js +0 -1
- package/dist/pixel/Darkness.js +50 -47
- package/dist/pixel/DitherVignette.js +0 -1
- package/dist/pixel/Evaporation.js +0 -1
- package/dist/pixel/NightShade.js +0 -1
- package/dist/pixel/PixelLayer.js +0 -1
- package/dist/pixel/PixelMarks.js +99 -0
- package/dist/pixel/SwayProp.js +0 -1
- package/dist/pixel/aimPixels.js +67 -0
- package/dist/pixel/lights.js +0 -1
- package/dist/pixel/nineSlice.js +0 -1
- package/dist/pixel/paintLayer.js +0 -1
- package/dist/pixel/palette.js +0 -1
- package/dist/pixel/pixel.js +0 -1
- package/dist/pixel/pixelPaint.js +27 -24
- package/dist/pixel/pixelParticles.js +35 -0
- package/dist/pixel/pixelTone.js +0 -1
- package/dist/pixel/propKit.js +0 -1
- package/dist/pixel/ramp.js +0 -1
- package/dist/pixel/selout.js +0 -1
- package/dist/pixel/spritePixels.js +0 -1
- package/dist/pixel/swayRig.js +0 -1
- package/dist/pixel/vignetteMask.js +0 -1
- package/dist/pixel/weatherArt.js +0 -1
- package/dist/pixel.js +71 -65
- package/dist/platform/appResume.js +18 -0
- package/dist/platform/fullscreen.js +52 -0
- package/dist/platform/orientation.js +26 -0
- package/dist/platform/pointerLock.js +37 -0
- package/dist/platform/reducedMotion.js +23 -0
- package/dist/platform/timeoutSignal.js +14 -0
- package/dist/platform/touch.js +0 -1
- package/dist/postfx/BloomFilter.js +0 -1
- package/dist/postfx/ChromaticAberrationFilter.js +0 -1
- package/dist/postfx/ColorAdjustFilter.js +0 -1
- package/dist/postfx/CrtFilter.js +0 -1
- package/dist/postfx/PixelateFilter.js +0 -1
- package/dist/postfx/PostFx.js +0 -1
- package/dist/postfx/ShockwaveFilter.js +0 -1
- package/dist/postfx/VignetteFilter.js +0 -1
- package/dist/postfx/fxAnimator.js +0 -1
- package/dist/postfx/fxShader.js +0 -1
- package/dist/postfx.js +0 -1
- package/dist/react/ChoiceDialog.js +0 -1
- package/dist/react/ConfirmDialog.js +0 -1
- package/dist/react/Glyph.js +0 -1
- package/dist/react/Modal.js +0 -1
- package/dist/react/Panel.js +0 -1
- package/dist/react/PixelImage.js +0 -1
- package/dist/react/Select.js +0 -1
- package/dist/react/Slider.js +0 -1
- package/dist/react/SplitPane.js +0 -1
- package/dist/react/TabStrip.js +0 -1
- package/dist/react/TitleTips.js +0 -1
- package/dist/react/Tooltip.js +0 -1
- package/dist/react/UiProvider.js +0 -1
- package/dist/react/baseCss.js +0 -1
- package/dist/react/choiceStore.js +0 -1
- package/dist/react/classNames.js +0 -1
- package/dist/react/confirmStore.js +0 -1
- package/dist/react/labels.js +0 -1
- package/dist/react/placement.js +0 -1
- package/dist/react/useFillSheet.js +0 -1
- package/dist/react/useFitToViewport.js +0 -1
- package/dist/react/usePicked.js +0 -1
- package/dist/react/useStore.js +0 -1
- package/dist/react/useViewport.js +0 -1
- package/dist/react.js +0 -1
- package/dist/rig/PosePlayer.js +0 -1
- package/dist/rig/RigView.js +0 -1
- package/dist/rig/Skeleton.js +0 -1
- package/dist/rig/humanoid.js +0 -1
- package/dist/rig/humanoid.json.js +0 -1
- package/dist/rig/humanoidPoses.js +0 -1
- package/dist/rig/poseMath.js +0 -1
- package/dist/rig/poses.json.js +0 -1
- package/dist/save/Autosaver.js +0 -1
- package/dist/save/MemoryBackend.js +0 -1
- package/dist/save/SaveStore.js +0 -1
- package/dist/save/WebStorageBackend.js +0 -1
- package/dist/save/checksum.js +0 -1
- package/dist/save/migrateSave.js +0 -1
- package/dist/settings.js +0 -1
- package/dist/tilemap/TilemapView.js +0 -1
- package/dist/tilemap/collision.js +0 -1
- package/dist/tilemap/tiled.js +0 -1
- package/dist/transitions/CrossfadeTransition.js +0 -1
- package/dist/transitions/DitherWipeTransition.js +0 -1
- package/dist/transitions/FadeTransition.js +0 -1
- package/dist/transitions/Letterbox.js +0 -1
- package/dist/transitions/ditherMask.js +0 -1
- package/dist/transitions/transitionTo.js +0 -1
- package/dist/transitions/tweenDone.js +0 -1
- package/dist/types/assets/AssetLoader.d.ts +63 -8
- package/dist/types/assets/AssetLoader.d.ts.map +1 -1
- package/dist/types/assets/textureAtlas.d.ts +35 -0
- package/dist/types/assets/textureAtlas.d.ts.map +1 -0
- package/dist/types/audio/SoundEngine.d.ts +5 -0
- package/dist/types/audio/SoundEngine.d.ts.map +1 -1
- package/dist/types/core/Camera.d.ts +1 -1
- package/dist/types/core/Camera.d.ts.map +1 -1
- package/dist/types/core/Clock.d.ts +14 -2
- package/dist/types/core/Clock.d.ts.map +1 -1
- package/dist/types/core/Pointer.d.ts +18 -0
- package/dist/types/core/Pointer.d.ts.map +1 -1
- package/dist/types/core/Stage.d.ts.map +1 -1
- package/dist/types/core/TextureRegistry.d.ts +39 -0
- package/dist/types/core/TextureRegistry.d.ts.map +1 -1
- package/dist/types/core/blendLayer.d.ts +23 -0
- package/dist/types/core/blendLayer.d.ts.map +1 -0
- package/dist/types/core/gameObject.d.ts.map +1 -1
- package/dist/types/core/pixelZoom.d.ts +45 -0
- package/dist/types/core/pixelZoom.d.ts.map +1 -0
- package/dist/types/core/runDue.d.ts +15 -0
- package/dist/types/core/runDue.d.ts.map +1 -0
- package/dist/types/core/silhouette.d.ts +16 -0
- package/dist/types/core/silhouette.d.ts.map +1 -0
- package/dist/types/debug/DrawCallCounter.d.ts.map +1 -1
- package/dist/types/engine/Engine.d.ts +16 -0
- package/dist/types/engine/Engine.d.ts.map +1 -1
- package/dist/types/engine/HiddenPause.d.ts +22 -0
- package/dist/types/engine/HiddenPause.d.ts.map +1 -0
- package/dist/types/fx/Afterimages.d.ts +62 -0
- package/dist/types/fx/Afterimages.d.ts.map +1 -0
- package/dist/types/index.d.ts +18 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/input/Actions.d.ts +7 -1
- package/dist/types/input/Actions.d.ts.map +1 -1
- package/dist/types/input/rumble.d.ts +41 -0
- package/dist/types/input/rumble.d.ts.map +1 -0
- package/dist/types/net/Connection.d.ts +22 -0
- package/dist/types/net/Connection.d.ts.map +1 -1
- package/dist/types/net/constants.d.ts +13 -4
- package/dist/types/net/constants.d.ts.map +1 -1
- package/dist/types/net/movement.d.ts.map +1 -1
- package/dist/types/net/reconnect.d.ts +14 -0
- package/dist/types/net/reconnect.d.ts.map +1 -0
- package/dist/types/pixel/Darkness.d.ts +17 -5
- package/dist/types/pixel/Darkness.d.ts.map +1 -1
- package/dist/types/pixel/PixelMarks.d.ts +68 -0
- package/dist/types/pixel/PixelMarks.d.ts.map +1 -0
- package/dist/types/pixel/aimPixels.d.ts +23 -0
- package/dist/types/pixel/aimPixels.d.ts.map +1 -0
- package/dist/types/pixel/index.d.ts +3 -1
- package/dist/types/pixel/index.d.ts.map +1 -1
- package/dist/types/pixel/pixelPaint.d.ts +1 -0
- package/dist/types/pixel/pixelPaint.d.ts.map +1 -1
- package/dist/types/pixel/pixelParticles.d.ts +21 -0
- package/dist/types/pixel/pixelParticles.d.ts.map +1 -0
- package/dist/types/platform/appResume.d.ts +25 -0
- package/dist/types/platform/appResume.d.ts.map +1 -0
- package/dist/types/platform/fullscreen.d.ts +37 -0
- package/dist/types/platform/fullscreen.d.ts.map +1 -0
- package/dist/types/platform/orientation.d.ts +19 -0
- package/dist/types/platform/orientation.d.ts.map +1 -0
- package/dist/types/platform/pointerLock.d.ts +26 -0
- package/dist/types/platform/pointerLock.d.ts.map +1 -0
- package/dist/types/platform/reducedMotion.d.ts +20 -0
- package/dist/types/platform/reducedMotion.d.ts.map +1 -0
- package/dist/types/platform/timeoutSignal.d.ts +6 -0
- package/dist/types/platform/timeoutSignal.d.ts.map +1 -0
- package/dist/types/ui/index.d.ts +4 -0
- package/dist/types/ui/index.d.ts.map +1 -1
- package/dist/types/ui/rich/RichText.d.ts +48 -0
- package/dist/types/ui/rich/RichText.d.ts.map +1 -0
- package/dist/types/ui/rich/richFonts.d.ts +70 -0
- package/dist/types/ui/rich/richFonts.d.ts.map +1 -0
- package/dist/types/ui/rich/richLayout.d.ts +76 -0
- package/dist/types/ui/rich/richLayout.d.ts.map +1 -0
- package/dist/types/ui/rich/richMarkup.d.ts +48 -0
- package/dist/types/ui/rich/richMarkup.d.ts.map +1 -0
- package/dist/ui/Bar.js +0 -1
- package/dist/ui/Button.js +0 -1
- package/dist/ui/Dialog.js +0 -1
- package/dist/ui/FocusManager.js +0 -1
- package/dist/ui/Label.js +0 -1
- package/dist/ui/List.js +0 -1
- package/dist/ui/Panel.js +0 -1
- package/dist/ui/ScrollView.js +0 -1
- package/dist/ui/Slider.js +0 -1
- package/dist/ui/Stack.js +0 -1
- package/dist/ui/Toggle.js +0 -1
- package/dist/ui/UiHost.js +0 -1
- package/dist/ui/UiPointerGate.js +0 -1
- package/dist/ui/UiRoot.js +0 -1
- package/dist/ui/Widget.js +0 -1
- package/dist/ui/dom/DomOverlay.js +0 -1
- package/dist/ui/dom/DomWorldAnchors.js +0 -1
- package/dist/ui/dom/InputShield.js +0 -1
- package/dist/ui/dom/createStore.js +0 -1
- package/dist/ui/dom/shieldDecision.js +0 -1
- package/dist/ui/dom/worldToScreen.js +0 -1
- package/dist/ui/layout/anchor.js +0 -1
- package/dist/ui/layout/barDrain.js +0 -1
- package/dist/ui/layout/fitToViewport.js +0 -1
- package/dist/ui/layout/focusNav.js +0 -1
- package/dist/ui/layout/geometry.js +0 -1
- package/dist/ui/layout/listWidth.js +0 -1
- package/dist/ui/layout/placeTooltip.js +0 -1
- package/dist/ui/layout/scroll.js +0 -1
- package/dist/ui/layout/slider.js +0 -1
- package/dist/ui/layout/stack.js +0 -1
- package/dist/ui/layout/wrapRow.js +0 -1
- package/dist/ui/rich/RichText.js +87 -0
- package/dist/ui/rich/richFonts.js +105 -0
- package/dist/ui/rich/richLayout.js +132 -0
- package/dist/ui/rich/richMarkup.js +67 -0
- package/dist/ui.js +75 -65
- package/package.json +1 -4
- package/dist/1681-engine.js.map +0 -1
- package/dist/ambient/ColorGrade.js.map +0 -1
- package/dist/ambient/DayClock.js.map +0 -1
- package/dist/ambient/WeatherLayer.js.map +0 -1
- package/dist/ambient/grade.js.map +0 -1
- package/dist/ambient/weather.js.map +0 -1
- package/dist/anim/AnimStateMachine.js.map +0 -1
- package/dist/anim/Animator.js.map +0 -1
- package/dist/anim/animStateCompile.js.map +0 -1
- package/dist/anim/animStateSystem.js.map +0 -1
- package/dist/anim/animationSystem.js.map +0 -1
- package/dist/anim/asepriteSlices.js.map +0 -1
- package/dist/anim/asepriteTags.js.map +0 -1
- package/dist/anim/parseAseprite.js.map +0 -1
- package/dist/assets/AssetLoader.js.map +0 -1
- package/dist/assets/loadAseprite.js.map +0 -1
- package/dist/assets/takeCleanerWorker-DTcI_Jqr.js.map +0 -1
- package/dist/audio/Footsteps.js.map +0 -1
- package/dist/audio/MusicPlayer.js.map +0 -1
- package/dist/audio/SoundEngine.js.map +0 -1
- package/dist/audio/cleanTake.js.map +0 -1
- package/dist/audio/music/ElementVoice.js.map +0 -1
- package/dist/audio/music/GraphVoice.js.map +0 -1
- package/dist/audio/music/MusicChannel.js.map +0 -1
- package/dist/audio/music/musicVoice.js.map +0 -1
- package/dist/audio/sampleBank.js.map +0 -1
- package/dist/audio/soundTable.js.map +0 -1
- package/dist/audio/synth.js.map +0 -1
- package/dist/core/BitmapLabel.js.map +0 -1
- package/dist/core/Camera.js.map +0 -1
- package/dist/core/Clock.js.map +0 -1
- package/dist/core/Graphics.js.map +0 -1
- package/dist/core/Group.js.map +0 -1
- package/dist/core/Image.js.map +0 -1
- package/dist/core/ObjectFactory.js.map +0 -1
- package/dist/core/Pointer.js.map +0 -1
- package/dist/core/Rectangle.js.map +0 -1
- package/dist/core/Stage.js.map +0 -1
- package/dist/core/Text.js.map +0 -1
- package/dist/core/TextureRegistry.js.map +0 -1
- package/dist/core/Timeline.js.map +0 -1
- package/dist/core/TimelineBuilder.js.map +0 -1
- package/dist/core/TimelineChild.js.map +0 -1
- package/dist/core/Tweener.js.map +0 -1
- package/dist/core/atlas.js.map +0 -1
- package/dist/core/atlasPlan.js.map +0 -1
- package/dist/core/eases.js.map +0 -1
- package/dist/core/gameObject.js.map +0 -1
- package/dist/core/pixiPatches.js.map +0 -1
- package/dist/core/timelinePlace.js.map +0 -1
- package/dist/core/tweenProps.js.map +0 -1
- package/dist/debug/DebugOverlay.js.map +0 -1
- package/dist/debug/DrawCallCounter.js.map +0 -1
- package/dist/debug/ErrorLog.js.map +0 -1
- package/dist/debug/FrameStats.js.map +0 -1
- package/dist/debug/inspectWorld.js.map +0 -1
- package/dist/diagnostics/ErrorDeduper.js.map +0 -1
- package/dist/diagnostics/FrameSampler.js.map +0 -1
- package/dist/diagnostics/bootWatch.js.map +0 -1
- package/dist/diagnostics/captureErrors.js.map +0 -1
- package/dist/diagnostics/contextLoss.js.map +0 -1
- package/dist/diagnostics/frameStats.js.map +0 -1
- package/dist/diagnostics/gpuInfo.js.map +0 -1
- package/dist/diagnostics/renderGuard.js.map +0 -1
- package/dist/ecs/SparseSet.js.map +0 -1
- package/dist/ecs/World.js.map +0 -1
- package/dist/ecs/fields.js.map +0 -1
- package/dist/engine/Engine.js.map +0 -1
- package/dist/engine/FixedLoop.js.map +0 -1
- package/dist/engine/Scene.js.map +0 -1
- package/dist/engine/SceneManager.js.map +0 -1
- package/dist/events/EventBus.js.map +0 -1
- package/dist/fx/FloatingText.js.map +0 -1
- package/dist/fx/hitStop.js.map +0 -1
- package/dist/fx/viewCull.js.map +0 -1
- package/dist/i18n/Translator.js.map +0 -1
- package/dist/i18n/extractStrings.js.map +0 -1
- package/dist/i18n/formatMessage.js.map +0 -1
- package/dist/input/Actions.js.map +0 -1
- package/dist/input/AimGesture.js.map +0 -1
- package/dist/input/Keyboard.js.map +0 -1
- package/dist/input/Keymap.js.map +0 -1
- package/dist/input/TargetLock.js.map +0 -1
- package/dist/input/aimAxis.js.map +0 -1
- package/dist/input/keyChord.js.map +0 -1
- package/dist/input/longPress.js.map +0 -1
- package/dist/input/stickAxis.js.map +0 -1
- package/dist/input/touchDetect.js.map +0 -1
- package/dist/light/LightLayer.js.map +0 -1
- package/dist/nav/MinHeap.js.map +0 -1
- package/dist/nav/findPath.js.map +0 -1
- package/dist/nav/navGrid.js.map +0 -1
- package/dist/nav/nearestOpen.js.map +0 -1
- package/dist/nav/straightenPath.js.map +0 -1
- package/dist/net/Connection.js.map +0 -1
- package/dist/net/InterpolationBuffer.js.map +0 -1
- package/dist/net/PredictedMover.js.map +0 -1
- package/dist/net/Room.js.map +0 -1
- package/dist/net/RoomHost.js.map +0 -1
- package/dist/net/cadence.js.map +0 -1
- package/dist/net/constants.js.map +0 -1
- package/dist/net/lobby/LobbyClient.js.map +0 -1
- package/dist/net/lobby/LobbyHost.js.map +0 -1
- package/dist/net/lobby/LobbyRooms.js.map +0 -1
- package/dist/net/lobby/Matchmaker.js.map +0 -1
- package/dist/net/movement.js.map +0 -1
- package/dist/net/protocol.js.map +0 -1
- package/dist/net/serverClock.js.map +0 -1
- package/dist/net/snapshotCodec.js.map +0 -1
- package/dist/parallax/Parallax.js.map +0 -1
- package/dist/parallax/ParallaxLayer.js.map +0 -1
- package/dist/parallax/parallaxMath.js.map +0 -1
- package/dist/particles/Emitter.js.map +0 -1
- package/dist/particles/ParticleBudget.js.map +0 -1
- package/dist/particles/ParticleSim.js.map +0 -1
- package/dist/particles/particleCurves.js.map +0 -1
- package/dist/particles/particleSampling.js.map +0 -1
- package/dist/physics/Body.js.map +0 -1
- package/dist/physics/BroadPhase.js.map +0 -1
- package/dist/physics/Contact.js.map +0 -1
- package/dist/physics/ContactSolver.js.map +0 -1
- package/dist/physics/PhysicsWorld.js.map +0 -1
- package/dist/physics/SpatialHash.js.map +0 -1
- package/dist/physics/collide.js.map +0 -1
- package/dist/physics/collidePolygon.js.map +0 -1
- package/dist/physics/contactReport.js.map +0 -1
- package/dist/physics/drawColliders.js.map +0 -1
- package/dist/physics/physicsSystem.js.map +0 -1
- package/dist/physics/polygon.js.map +0 -1
- package/dist/physics/raycast.js.map +0 -1
- package/dist/physics/syncToBody.js.map +0 -1
- package/dist/physics/tileBodies.js.map +0 -1
- package/dist/physics/types.js.map +0 -1
- package/dist/pixel/Darkness.js.map +0 -1
- package/dist/pixel/DitherVignette.js.map +0 -1
- package/dist/pixel/Evaporation.js.map +0 -1
- package/dist/pixel/NightShade.js.map +0 -1
- package/dist/pixel/PixelLayer.js.map +0 -1
- package/dist/pixel/SwayProp.js.map +0 -1
- package/dist/pixel/lights.js.map +0 -1
- package/dist/pixel/nineSlice.js.map +0 -1
- package/dist/pixel/paintLayer.js.map +0 -1
- package/dist/pixel/palette.js.map +0 -1
- package/dist/pixel/pixel.js.map +0 -1
- package/dist/pixel/pixelPaint.js.map +0 -1
- package/dist/pixel/pixelTone.js.map +0 -1
- package/dist/pixel/propKit.js.map +0 -1
- package/dist/pixel/ramp.js.map +0 -1
- package/dist/pixel/selout.js.map +0 -1
- package/dist/pixel/spritePixels.js.map +0 -1
- package/dist/pixel/swayRig.js.map +0 -1
- package/dist/pixel/vignetteMask.js.map +0 -1
- package/dist/pixel/weatherArt.js.map +0 -1
- package/dist/pixel.js.map +0 -1
- package/dist/platform/touch.js.map +0 -1
- package/dist/postfx/BloomFilter.js.map +0 -1
- package/dist/postfx/ChromaticAberrationFilter.js.map +0 -1
- package/dist/postfx/ColorAdjustFilter.js.map +0 -1
- package/dist/postfx/CrtFilter.js.map +0 -1
- package/dist/postfx/PixelateFilter.js.map +0 -1
- package/dist/postfx/PostFx.js.map +0 -1
- package/dist/postfx/ShockwaveFilter.js.map +0 -1
- package/dist/postfx/VignetteFilter.js.map +0 -1
- package/dist/postfx/fxAnimator.js.map +0 -1
- package/dist/postfx/fxShader.js.map +0 -1
- package/dist/postfx.js.map +0 -1
- package/dist/react/ChoiceDialog.js.map +0 -1
- package/dist/react/ConfirmDialog.js.map +0 -1
- package/dist/react/Glyph.js.map +0 -1
- package/dist/react/Modal.js.map +0 -1
- package/dist/react/Panel.js.map +0 -1
- package/dist/react/PixelImage.js.map +0 -1
- package/dist/react/Select.js.map +0 -1
- package/dist/react/Slider.js.map +0 -1
- package/dist/react/SplitPane.js.map +0 -1
- package/dist/react/TabStrip.js.map +0 -1
- package/dist/react/TitleTips.js.map +0 -1
- package/dist/react/Tooltip.js.map +0 -1
- package/dist/react/UiProvider.js.map +0 -1
- package/dist/react/baseCss.js.map +0 -1
- package/dist/react/choiceStore.js.map +0 -1
- package/dist/react/classNames.js.map +0 -1
- package/dist/react/confirmStore.js.map +0 -1
- package/dist/react/labels.js.map +0 -1
- package/dist/react/placement.js.map +0 -1
- package/dist/react/useFillSheet.js.map +0 -1
- package/dist/react/useFitToViewport.js.map +0 -1
- package/dist/react/usePicked.js.map +0 -1
- package/dist/react/useStore.js.map +0 -1
- package/dist/react/useViewport.js.map +0 -1
- package/dist/react.js.map +0 -1
- package/dist/rig/PosePlayer.js.map +0 -1
- package/dist/rig/RigView.js.map +0 -1
- package/dist/rig/Skeleton.js.map +0 -1
- package/dist/rig/humanoid.js.map +0 -1
- package/dist/rig/humanoid.json.js.map +0 -1
- package/dist/rig/humanoidPoses.js.map +0 -1
- package/dist/rig/poseMath.js.map +0 -1
- package/dist/rig/poses.json.js.map +0 -1
- package/dist/save/Autosaver.js.map +0 -1
- package/dist/save/MemoryBackend.js.map +0 -1
- package/dist/save/SaveStore.js.map +0 -1
- package/dist/save/WebStorageBackend.js.map +0 -1
- package/dist/save/checksum.js.map +0 -1
- package/dist/save/migrateSave.js.map +0 -1
- package/dist/settings.js.map +0 -1
- package/dist/tilemap/TilemapView.js.map +0 -1
- package/dist/tilemap/collision.js.map +0 -1
- package/dist/tilemap/tiled.js.map +0 -1
- package/dist/transitions/CrossfadeTransition.js.map +0 -1
- package/dist/transitions/DitherWipeTransition.js.map +0 -1
- package/dist/transitions/FadeTransition.js.map +0 -1
- package/dist/transitions/Letterbox.js.map +0 -1
- package/dist/transitions/ditherMask.js.map +0 -1
- package/dist/transitions/transitionTo.js.map +0 -1
- package/dist/transitions/tweenDone.js.map +0 -1
- package/dist/ui/Bar.js.map +0 -1
- package/dist/ui/Button.js.map +0 -1
- package/dist/ui/Dialog.js.map +0 -1
- package/dist/ui/FocusManager.js.map +0 -1
- package/dist/ui/Label.js.map +0 -1
- package/dist/ui/List.js.map +0 -1
- package/dist/ui/Panel.js.map +0 -1
- package/dist/ui/ScrollView.js.map +0 -1
- package/dist/ui/Slider.js.map +0 -1
- package/dist/ui/Stack.js.map +0 -1
- package/dist/ui/Toggle.js.map +0 -1
- package/dist/ui/UiHost.js.map +0 -1
- package/dist/ui/UiPointerGate.js.map +0 -1
- package/dist/ui/UiRoot.js.map +0 -1
- package/dist/ui/Widget.js.map +0 -1
- package/dist/ui/dom/DomOverlay.js.map +0 -1
- package/dist/ui/dom/DomWorldAnchors.js.map +0 -1
- package/dist/ui/dom/InputShield.js.map +0 -1
- package/dist/ui/dom/createStore.js.map +0 -1
- package/dist/ui/dom/shieldDecision.js.map +0 -1
- package/dist/ui/dom/worldToScreen.js.map +0 -1
- package/dist/ui/layout/anchor.js.map +0 -1
- package/dist/ui/layout/barDrain.js.map +0 -1
- package/dist/ui/layout/fitToViewport.js.map +0 -1
- package/dist/ui/layout/focusNav.js.map +0 -1
- package/dist/ui/layout/geometry.js.map +0 -1
- package/dist/ui/layout/listWidth.js.map +0 -1
- package/dist/ui/layout/placeTooltip.js.map +0 -1
- package/dist/ui/layout/scroll.js.map +0 -1
- package/dist/ui/layout/slider.js.map +0 -1
- package/dist/ui/layout/stack.js.map +0 -1
- package/dist/ui/layout/wrapRow.js.map +0 -1
- package/dist/ui.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Letterbox.js","sources":["../../src/transitions/Letterbox.ts"],"sourcesContent":["import { Container, Sprite, Texture } from 'pixi.js';\nimport type { Stage } from '../core/Stage';\nimport { tweenDone } from './tweenDone';\n\nexport interface LetterboxOptions {\n /** Each bar's height as a share of the screen's (default 0.12). */\n share?: number;\n /** `0xrrggbb` (default black). */\n color?: number;\n}\n\n/**\n * Cinema bars for a cut scene: two bars slide in from the top and bottom of the screen and back out. They live on\n * the stage's screen layer, above what was there when they were made; a HUD added later draws over them, so hide it\n * for the scene. Bar heights are whole screen pixels at every step of the slide.\n */\nexport class Letterbox {\n readonly view = new Container();\n private readonly top = new Sprite(Texture.WHITE);\n private readonly bottom = new Sprite(Texture.WHITE);\n /** 0 hidden, 1 fully in. */\n private readonly state = { k: 0 };\n private readonly stopResize: () => void;\n\n constructor(\n private readonly stage: Stage,\n private readonly options: LetterboxOptions = {},\n ) {\n for (const bar of [this.top, this.bottom]) bar.tint = options.color ?? 0x000000;\n this.view.addChild(this.top, this.bottom);\n this.view.visible = false;\n stage.screen.addChild(this.view);\n this.stopResize = stage.onResize(() => this.layout());\n this.layout();\n }\n\n /** Whether the bars are (partly) in. */\n get shown(): boolean {\n return this.state.k > 0;\n }\n\n /** Slides the bars in over `ms` (0: at once). */\n show(ms = 400): Promise<void> {\n return this.to(1, ms);\n }\n\n hide(ms = 400): Promise<void> {\n return this.to(0, ms);\n }\n\n destroy(): void {\n this.stage.tweens.killTweensOf(this.state);\n this.stopResize();\n this.view.destroy({ children: true });\n }\n\n private to(k: number, ms: number): Promise<void> {\n this.stage.tweens.killTweensOf(this.state);\n if (ms <= 0) {\n this.state.k = k;\n this.layout();\n return Promise.resolve();\n }\n return tweenDone(this.stage, { targets: this.state, k, duration: ms, ease: 'Sine.InOut', onUpdate: () => this.layout() });\n }\n\n private layout(): void {\n const w = this.stage.width;\n const h = this.stage.height;\n const bar = Math.round(h * (this.options.share ?? 0.12) * this.state.k);\n this.view.visible = bar > 0;\n this.top.width = w;\n this.top.height = bar;\n this.bottom.width = w;\n this.bottom.height = bar;\n this.bottom.y = h - bar;\n }\n}\n"],"names":["Letterbox","stage","options","bar","Container","Sprite","Texture","ms","k","tweenDone","w","h"],"mappings":";;AAgBO,MAAMA,EAAU;AAAA,EAQrB,YACmBC,GACAC,IAA4B,IAC7C;AAFiB,SAAA,QAAAD,GACA,KAAA,UAAAC;AAEjB,eAAWC,KAAO,CAAC,KAAK,KAAK,KAAK,MAAM,EAAG,CAAAA,EAAI,OAAOD,EAAQ,SAAS;AACvE,SAAK,KAAK,SAAS,KAAK,KAAK,KAAK,MAAM,GACxC,KAAK,KAAK,UAAU,IACpBD,EAAM,OAAO,SAAS,KAAK,IAAI,GAC/B,KAAK,aAAaA,EAAM,SAAS,MAAM,KAAK,QAAQ,GACpD,KAAK,OAAA;AAAA,EACP;AAAA,EATmB;AAAA,EACA;AAAA,EATV,OAAO,IAAIG,EAAA;AAAA,EACH,MAAM,IAAIC,EAAOC,EAAQ,KAAK;AAAA,EAC9B,SAAS,IAAID,EAAOC,EAAQ,KAAK;AAAA;AAAA,EAEjC,QAAQ,EAAE,GAAG,EAAA;AAAA,EACb;AAAA;AAAA,EAejB,IAAI,QAAiB;AACnB,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB;AAAA;AAAA,EAGA,KAAKC,IAAK,KAAoB;AAC5B,WAAO,KAAK,GAAG,GAAGA,CAAE;AAAA,EACtB;AAAA,EAEA,KAAKA,IAAK,KAAoB;AAC5B,WAAO,KAAK,GAAG,GAAGA,CAAE;AAAA,EACtB;AAAA,EAEA,UAAgB;AACd,SAAK,MAAM,OAAO,aAAa,KAAK,KAAK,GACzC,KAAK,WAAA,GACL,KAAK,KAAK,QAAQ,EAAE,UAAU,IAAM;AAAA,EACtC;AAAA,EAEQ,GAAGC,GAAWD,GAA2B;AAE/C,WADA,KAAK,MAAM,OAAO,aAAa,KAAK,KAAK,GACrCA,KAAM,KACR,KAAK,MAAM,IAAIC,GACf,KAAK,OAAA,GACE,QAAQ,QAAA,KAEVC,EAAU,KAAK,OAAO,EAAE,SAAS,KAAK,OAAO,GAAAD,GAAG,UAAUD,GAAI,MAAM,cAAc,UAAU,MAAM,KAAK,OAAA,GAAU;AAAA,EAC1H;AAAA,EAEQ,SAAe;AACrB,UAAMG,IAAI,KAAK,MAAM,OACfC,IAAI,KAAK,MAAM,QACfR,IAAM,KAAK,MAAMQ,KAAK,KAAK,QAAQ,SAAS,QAAQ,KAAK,MAAM,CAAC;AACtE,SAAK,KAAK,UAAUR,IAAM,GAC1B,KAAK,IAAI,QAAQO,GACjB,KAAK,IAAI,SAASP,GAClB,KAAK,OAAO,QAAQO,GACpB,KAAK,OAAO,SAASP,GACrB,KAAK,OAAO,IAAIQ,IAAIR;AAAA,EACtB;AACF;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ditherMask.js","sources":["../../src/transitions/ditherMask.ts"],"sourcesContent":["/** The 4×4 ordered-dither (Bayer) matrix, thresholds 0–15 row by row. */\nexport const BAYER4: readonly number[] = [0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5];\n\n/** Dither levels from empty (0) to solid (16): a level n covers the n cells of the 4×4 tile with the lowest thresholds. */\nexport const DITHER_LEVELS = 16;\n\n/** Whether cell (x, y) of a tiled 4×4 pattern is covered at `level` (0–16). */\nexport function ditherCovered(level: number, x: number, y: number): boolean {\n return BAYER4[(y & 3) * 4 + (x & 3)]! < level;\n}\n\n/**\n * The dither level of one band of a wipe. `progress` 0–1 runs the wipe; `position` 0–1 is the band's centre along\n * the direction of travel; `softness` (0–1) is how much of the screen the dithered edge spans. At progress 0 every\n * band is empty, at 1 every band is solid, and in between the edge sweeps across with its gradient in dither steps.\n */\nexport function wipeLevel(progress: number, position: number, softness: number): number {\n const soft = Math.max(1e-3, softness);\n const front = progress * (1 + soft);\n const local = (front - position) / soft;\n const k = local <= 0 ? 0 : local >= 1 ? 1 : local;\n return Math.round(k * DITHER_LEVELS);\n}\n"],"names":["BAYER4","DITHER_LEVELS","ditherCovered","level","x","y","wipeLevel","progress","position","softness","soft","local","k"],"mappings":"AACO,MAAMA,IAA4B,CAAC,GAAG,GAAG,GAAG,IAAI,IAAI,GAAG,IAAI,GAAG,GAAG,IAAI,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC,GAGjFC,IAAgB;AAGtB,SAASC,EAAcC,GAAeC,GAAWC,GAAoB;AAC1E,SAAOL,GAAQK,IAAI,KAAK,KAAKD,IAAI,EAAE,IAAKD;AAC1C;AAOO,SAASG,EAAUC,GAAkBC,GAAkBC,GAA0B;AACtF,QAAMC,IAAO,KAAK,IAAI,MAAMD,CAAQ,GAE9BE,KADQJ,KAAY,IAAIG,KACPF,KAAYE,GAC7BE,IAAID,KAAS,IAAI,IAAIA,KAAS,IAAI,IAAIA;AAC5C,SAAO,KAAK,MAAMC,IAAIX,CAAa;AACrC;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"transitionTo.js","sources":["../../src/transitions/transitionTo.ts"],"sourcesContent":["import { Container } from 'pixi.js';\nimport type { Engine } from '../engine/Engine';\nimport type { Scene } from '../engine/Scene';\nimport type { Transition, TransitionContext } from './Transition';\n\n/** How the scene stack changes under the transition. */\nexport type TransitionMode = 'replace' | 'push' | 'pop';\n\n/** Transitions running per engine, so a second one waits for the first instead of drawing over it. */\nconst running = new WeakMap<Engine, Promise<void>>();\n\n/**\n * Changes scene behind a transition: covers the screen, runs `engine.scenes.replace(next)` (or `push`, or `pop`, for\n * which `next` is ignored) — waiting out an async `onEnter` while covered — then reveals. Resolves when the reveal\n * ends. The overlay is removed and the transition disposed even when the scene change throws, which is re-thrown.\n *\n * ```ts\n * await transitionTo(engine, new Level2(), new FadeTransition({ duration: 250 }));\n * ```\n */\nexport function transitionTo(engine: Engine, next: Scene | null, transition: Transition, mode: TransitionMode = 'replace'): Promise<void> {\n const before = running.get(engine) ?? Promise.resolve();\n const job = before.then(() => run(engine, next, transition, mode));\n const tail = job.catch(() => undefined);\n running.set(engine, tail);\n void tail.then(() => {\n if (running.get(engine) === tail) running.delete(engine);\n });\n return job;\n}\n\nasync function run(engine: Engine, next: Scene | null, transition: Transition, mode: TransitionMode): Promise<void> {\n const layer = new Container();\n // over the screen layer, so it covers every scene's HUD too, including the HUD the next scene is about to add\n engine.app.stage.addChild(layer);\n const ctx: TransitionContext = { stage: engine.stage, app: engine.app, layer };\n try {\n await transition.cover(ctx);\n if (mode === 'pop') await engine.scenes.pop();\n else {\n if (!next) throw new Error(`transitionTo: '${mode}' needs a scene`);\n await (mode === 'push' ? engine.scenes.push(next) : engine.scenes.replace(next));\n }\n await transition.reveal(ctx);\n } finally {\n transition.dispose();\n layer.destroy({ children: true });\n }\n}\n"],"names":["running","transitionTo","engine","next","transition","mode","job","run","tail","layer","Container","ctx"],"mappings":";AASA,MAAMA,wBAAc,QAAA;AAWb,SAASC,EAAaC,GAAgBC,GAAoBC,GAAwBC,IAAuB,WAA0B;AAExI,QAAMC,KADSN,EAAQ,IAAIE,CAAM,KAAK,QAAQ,QAAA,GAC3B,KAAK,MAAMK,EAAIL,GAAQC,GAAMC,GAAYC,CAAI,CAAC,GAC3DG,IAAOF,EAAI,MAAM,MAAA;AAAA,GAAe;AACtC,SAAAN,EAAQ,IAAIE,GAAQM,CAAI,GACnBA,EAAK,KAAK,MAAM;AACnB,IAAIR,EAAQ,IAAIE,CAAM,MAAMM,KAAMR,EAAQ,OAAOE,CAAM;AAAA,EACzD,CAAC,GACMI;AACT;AAEA,eAAeC,EAAIL,GAAgBC,GAAoBC,GAAwBC,GAAqC;AAClH,QAAMI,IAAQ,IAAIC,EAAA;AAElB,EAAAR,EAAO,IAAI,MAAM,SAASO,CAAK;AAC/B,QAAME,IAAyB,EAAE,OAAOT,EAAO,OAAO,KAAKA,EAAO,KAAK,OAAAO,EAAA;AACvE,MAAI;AAEF,QADA,MAAML,EAAW,MAAMO,CAAG,GACtBN,MAAS,MAAO,OAAMH,EAAO,OAAO,IAAA;AAAA,SACnC;AACH,UAAI,CAACC,EAAM,OAAM,IAAI,MAAM,kBAAkBE,CAAI,iBAAiB;AAClE,aAAOA,MAAS,SAASH,EAAO,OAAO,KAAKC,CAAI,IAAID,EAAO,OAAO,QAAQC,CAAI;AAAA,IAChF;AACA,UAAMC,EAAW,OAAOO,CAAG;AAAA,EAC7B,UAAA;AACE,IAAAP,EAAW,QAAA,GACXK,EAAM,QAAQ,EAAE,UAAU,GAAA,CAAM;AAAA,EAClC;AACF;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tweenDone.js","sources":["../../src/transitions/tweenDone.ts"],"sourcesContent":["import type { Stage } from '../core/Stage';\nimport type { TweenConfig } from '../core/Tweener';\n\n/** Runs a tween on the stage and resolves when it completes: the awaitable form transitions are built from. */\nexport function tweenDone(stage: Stage, config: TweenConfig): Promise<void> {\n return new Promise((resolve) => {\n const done = config.onComplete;\n stage.tweens.add({\n ...config,\n onComplete: () => {\n done?.();\n resolve();\n },\n });\n });\n}\n"],"names":["tweenDone","stage","config","resolve","done"],"mappings":"AAIO,SAASA,EAAUC,GAAcC,GAAoC;AAC1E,SAAO,IAAI,QAAQ,CAACC,MAAY;AAC9B,UAAMC,IAAOF,EAAO;AACpB,IAAAD,EAAM,OAAO,IAAI;AAAA,MACf,GAAGC;AAAA,MACH,YAAY,MAAM;AAChB,QAAAE,IAAA,GACAD,EAAA;AAAA,MACF;AAAA,IAAA,CACD;AAAA,EACH,CAAC;AACH;"}
|
package/dist/ui/Bar.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Bar.js","sources":["../../src/ui/Bar.ts"],"sourcesContent":["import { Sprite, Texture } from 'pixi.js';\nimport { drainState, setDrainValue, stepDrain, type DrainOptions, type DrainState } from './layout/barDrain';\nimport type { UiTicking } from './UiHost';\nimport { Widget } from './Widget';\n\nexport interface BarOptions extends DrainOptions {\n width: number;\n height: number;\n /** The bar itself. */\n color: number;\n /** The trail behind it after a loss (default white at 0.6 alpha). */\n trailColor?: number;\n trailAlpha?: number;\n /** The empty part (default black at half alpha). */\n backColor?: number;\n backAlpha?: number;\n /** Fills upward instead of rightward. */\n vertical?: boolean;\n /** Starting value, 0–1 (default 1). */\n value?: number;\n}\n\n/** One white texel stretched and tinted: three sprites a bar, no Graphics rebuilt while it drains. */\nfunction strip(color: number, alpha: number): Sprite {\n const s = new Sprite(Texture.WHITE);\n s.tint = color;\n s.alpha = alpha;\n return s;\n}\n\n/** A fraction of a length, on whole pixels when `snap`. */\nfunction part(f: number, full: number, snap: boolean): number {\n return snap ? Math.round(f * full) : f * full;\n}\n\n/**\n * A health or progress bar with a delayed drain: a loss shows as a trail that holds a moment, then drains down to\n * the bar, so the player sees how much a hit took. Ticks only while the trail is draining.\n */\nexport class Bar extends Widget implements UiTicking {\n private readonly state: DrainState;\n private readonly back: Sprite;\n private readonly trail: Sprite;\n private readonly fill: Sprite;\n private ticking = false;\n\n constructor(private readonly options: BarOptions) {\n super();\n this.state = drainState(options.value ?? 1);\n this.back = strip(options.backColor ?? 0x000000, options.backAlpha ?? 0.5);\n this.trail = strip(options.trailColor ?? 0xffffff, options.trailAlpha ?? 0.6);\n this.fill = strip(options.color, 1);\n this.addChild(this.back, this.trail, this.fill);\n this.resize(options.width, options.height);\n }\n\n get value(): number {\n return this.state.value;\n }\n\n /** The trail's length (what was held before the last losses), 0–1. */\n get trailValue(): number {\n return this.state.trail;\n }\n\n /** Sets the bar, 0–1; `instant` drops the trail with it (a respawn, a new target). */\n setValue(value: number, instant = false): this {\n if (value === this.state.value && !instant) return this;\n setDrainValue(this.state, value, this.options);\n if (instant) this.state.trail = this.state.value;\n this.redraw();\n if (this.state.trail > this.state.value && !this.ticking) {\n const host = this.host;\n if (host) {\n this.ticking = true;\n host.animate(this);\n }\n }\n return this;\n }\n\n /** Advances the drain; a bar under a `UiRoot` is ticked for you, one standing alone is ticked by the game. */\n tick(dtMs: number): boolean {\n if (this.destroyed) return (this.ticking = false);\n stepDrain(this.state, dtMs, this.options);\n this.redraw();\n this.ticking = this.state.trail > this.state.value;\n return this.ticking;\n }\n\n protected redraw(): void {\n const w = this.boxW;\n const h = this.boxH;\n const snap = this.host?.pixelPerfect ?? false;\n this.back.setSize(w, h);\n if (this.options.vertical) {\n const t = part(this.state.trail, h, snap);\n const v = part(this.state.value, h, snap);\n this.trail.setSize(w, t);\n this.trail.position.set(0, h - t);\n this.fill.setSize(w, v);\n this.fill.position.set(0, h - v);\n } else {\n this.trail.setSize(part(this.state.trail, w, snap), h);\n this.fill.setSize(part(this.state.value, w, snap), h);\n }\n this.alpha = this.enabled ? 1 : 0.5;\n }\n}\n"],"names":["strip","color","alpha","s","Sprite","Texture","part","f","full","snap","Bar","Widget","options","drainState","value","instant","setDrainValue","host","dtMs","stepDrain","w","h","t","v"],"mappings":";;;AAuBA,SAASA,EAAMC,GAAeC,GAAuB;AACnD,QAAMC,IAAI,IAAIC,EAAOC,EAAQ,KAAK;AAClC,SAAAF,EAAE,OAAOF,GACTE,EAAE,QAAQD,GACHC;AACT;AAGA,SAASG,EAAKC,GAAWC,GAAcC,GAAuB;AAC5D,SAAOA,IAAO,KAAK,MAAMF,IAAIC,CAAI,IAAID,IAAIC;AAC3C;AAMO,MAAME,UAAYC,EAA4B;AAAA,EAOnD,YAA6BC,GAAqB;AAChD,UAAA,GAD2B,KAAA,UAAAA,GAE3B,KAAK,QAAQC,EAAWD,EAAQ,SAAS,CAAC,GAC1C,KAAK,OAAOZ,EAAMY,EAAQ,aAAa,GAAUA,EAAQ,aAAa,GAAG,GACzE,KAAK,QAAQZ,EAAMY,EAAQ,cAAc,UAAUA,EAAQ,cAAc,GAAG,GAC5E,KAAK,OAAOZ,EAAMY,EAAQ,OAAO,CAAC,GAClC,KAAK,SAAS,KAAK,MAAM,KAAK,OAAO,KAAK,IAAI,GAC9C,KAAK,OAAOA,EAAQ,OAAOA,EAAQ,MAAM;AAAA,EAC3C;AAAA,EAR6B;AAAA,EANZ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACT,UAAU;AAAA,EAYlB,IAAI,QAAgB;AAClB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA;AAAA,EAGA,IAAI,aAAqB;AACvB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA;AAAA,EAGA,SAASE,GAAeC,IAAU,IAAa;AAC7C,QAAID,MAAU,KAAK,MAAM,SAAS,CAACC,EAAS,QAAO;AAInD,QAHAC,EAAc,KAAK,OAAOF,GAAO,KAAK,OAAO,GACzCC,MAAS,KAAK,MAAM,QAAQ,KAAK,MAAM,QAC3C,KAAK,OAAA,GACD,KAAK,MAAM,QAAQ,KAAK,MAAM,SAAS,CAAC,KAAK,SAAS;AACxD,YAAME,IAAO,KAAK;AAClB,MAAIA,MACF,KAAK,UAAU,IACfA,EAAK,QAAQ,IAAI;AAAA,IAErB;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,KAAKC,GAAuB;AAC1B,WAAI,KAAK,YAAmB,KAAK,UAAU,MAC3CC,EAAU,KAAK,OAAOD,GAAM,KAAK,OAAO,GACxC,KAAK,OAAA,GACL,KAAK,UAAU,KAAK,MAAM,QAAQ,KAAK,MAAM,OACtC,KAAK;AAAA,EACd;AAAA,EAEU,SAAe;AACvB,UAAME,IAAI,KAAK,MACTC,IAAI,KAAK,MACTZ,IAAO,KAAK,MAAM,gBAAgB;AAExC,QADA,KAAK,KAAK,QAAQW,GAAGC,CAAC,GAClB,KAAK,QAAQ,UAAU;AACzB,YAAMC,IAAIhB,EAAK,KAAK,MAAM,OAAOe,GAAGZ,CAAI,GAClCc,IAAIjB,EAAK,KAAK,MAAM,OAAOe,GAAGZ,CAAI;AACxC,WAAK,MAAM,QAAQW,GAAGE,CAAC,GACvB,KAAK,MAAM,SAAS,IAAI,GAAGD,IAAIC,CAAC,GAChC,KAAK,KAAK,QAAQF,GAAGG,CAAC,GACtB,KAAK,KAAK,SAAS,IAAI,GAAGF,IAAIE,CAAC;AAAA,IACjC;AACE,WAAK,MAAM,QAAQjB,EAAK,KAAK,MAAM,OAAOc,GAAGX,CAAI,GAAGY,CAAC,GACrD,KAAK,KAAK,QAAQf,EAAK,KAAK,MAAM,OAAOc,GAAGX,CAAI,GAAGY,CAAC;AAEtD,SAAK,QAAQ,KAAK,UAAU,IAAI;AAAA,EAClC;AACF;"}
|
package/dist/ui/Button.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Button.js","sources":["../../src/ui/Button.ts"],"sourcesContent":["import { Container, Rectangle } from 'pixi.js';\nimport { Panel, type PanelSkin } from './Panel';\nimport { sizeOf, Widget } from './Widget';\n\nexport type ButtonState = 'normal' | 'hover' | 'pressed' | 'disabled';\n\nexport interface ButtonSkin {\n normal: PanelSkin;\n hover?: PanelSkin;\n pressed?: PanelSkin;\n disabled?: PanelSkin;\n /** Drawn over the face while it has keyboard or gamepad focus (a ring, a brighter frame). */\n focus?: PanelSkin;\n /** How far the content drops while pressed (px, default 1): a pressed face is the same face a pixel lower. */\n pressOffset?: number;\n}\n\nexport interface ButtonOptions {\n width: number;\n height: number;\n skin: ButtonSkin;\n /**\n * Centred on the face: a `Label`, an icon, anything. A widget that changes size by itself (a `Label` given new\n * text) is centred again at once; anything else, after `resize` or a state change.\n */\n content?: Container;\n onClick?: () => void;\n}\n\n/** Walks up for a scroll view that was just dragged: lifting a finger after scrolling a list is not a tap. */\nfunction tapSuppressed(c: Container | null): boolean {\n for (let p = c; p; p = p.parent) if ((p as { suppressesTaps?: boolean }).suppressesTaps === true) return true;\n return false;\n}\n\n/**\n * A pressable button: normal, hover, pressed and disabled faces, a focus overlay, pointer and touch through Pixi's\n * events, and Accept from the keyboard or a gamepad through the focus manager.\n */\nexport class Button extends Widget {\n onClick: (() => void) | null;\n private readonly faces = new Map<PanelSkin, Panel>();\n private readonly focusFace: Panel | null;\n private readonly content: Container | null;\n private hovered = false;\n private pressing = false;\n\n constructor(private readonly options: ButtonOptions) {\n super();\n this.focusable = true;\n this.onClick = options.onClick ?? null;\n const skin = options.skin;\n // one panel per distinct skin, shown in turn: no redraw when the state changes\n for (const s of [skin.normal, skin.hover, skin.pressed, skin.disabled]) {\n if (s && !this.faces.has(s)) {\n const face = new Panel(s, options.width, options.height, { blocksPointer: false });\n this.faces.set(s, face);\n this.addChild(face);\n }\n }\n this.focusFace = skin.focus ? new Panel(skin.focus, options.width, options.height, { blocksPointer: false }) : null;\n if (this.focusFace) this.addChild(this.focusFace);\n this.content = options.content ?? null;\n if (this.content) this.addChild(this.content);\n this.makeInteractive();\n this.cursor = 'pointer';\n this.hitArea = new Rectangle(0, 0, 0, 0);\n this.on('pointerover', () => this.setPointer(true, this.pressing));\n this.on('pointerout', () => this.setPointer(false, this.pressing));\n this.on('pointerdown', () => this.setPointer(this.hovered, true));\n this.on('pointerup', () => this.setPointer(this.hovered, false));\n this.on('pointerupoutside', () => this.setPointer(false, false));\n this.on('pointercancel', () => this.setPointer(false, false));\n // a tap: down and up on the button, by mouse, pen or finger\n this.on('pointertap', () => {\n if (!tapSuppressed(this)) this.click();\n });\n this.resize(options.width, options.height);\n }\n\n get state(): ButtonState {\n if (!this.enabled) return 'disabled';\n if (this.pressing) return 'pressed';\n return this.hovered ? 'hover' : 'normal';\n }\n\n /** Runs the button's action, if it is enabled. */\n click(): void {\n if (this.enabled) this.onClick?.();\n }\n\n override activate(): void {\n this.click();\n }\n\n protected override childResized(child: Widget): void {\n if (child === this.content) this.redraw();\n }\n\n private setPointer(hovered: boolean, pressing: boolean): void {\n if (hovered === this.hovered && pressing === this.pressing) return;\n this.hovered = hovered;\n this.pressing = pressing;\n this.redraw();\n }\n\n protected redraw(): void {\n const skin = this.options.skin;\n const state = this.state;\n const wanted = (state === 'disabled' ? skin.disabled : state === 'pressed' ? (skin.pressed ?? skin.hover) : state === 'hover' ? skin.hover : undefined) ?? skin.normal;\n for (const [s, face] of this.faces) {\n face.visible = s === wanted;\n if (face.boxWidth !== this.boxW || face.boxHeight !== this.boxH) face.resize(this.boxW, this.boxH);\n }\n if (this.focusFace) {\n this.focusFace.visible = this.focused && this.enabled;\n if (this.focusFace.boxWidth !== this.boxW || this.focusFace.boxHeight !== this.boxH) this.focusFace.resize(this.boxW, this.boxH);\n }\n if (this.hitArea instanceof Rectangle) {\n this.hitArea.width = this.boxW;\n this.hitArea.height = this.boxH;\n }\n if (this.content) {\n const s = sizeOf(this.content);\n const drop = state === 'pressed' ? (skin.pressOffset ?? 1) : 0;\n const snap = this.host?.pixelPerfect ?? false;\n const x = (this.boxW - s.width) / 2;\n const y = (this.boxH - s.height) / 2 + drop;\n this.content.position.set(snap ? Math.round(x) : x, snap ? Math.round(y) : y);\n }\n // without a disabled face, a disabled button fades\n this.alpha = state === 'disabled' && !skin.disabled ? 0.5 : 1;\n }\n}\n"],"names":["tapSuppressed","c","p","Button","Widget","options","skin","s","face","Panel","Rectangle","child","hovered","pressing","state","wanted","sizeOf","drop","snap","x","y"],"mappings":";;;AA8BA,SAASA,EAAcC,GAA8B;AACnD,WAASC,IAAID,GAAGC,GAAGA,IAAIA,EAAE,OAAQ,KAAKA,EAAmC,mBAAmB,GAAM,QAAO;AACzG,SAAO;AACT;AAMO,MAAMC,UAAeC,EAAO;AAAA,EAQjC,YAA6BC,GAAwB;AACnD,UAAA,GAD2B,KAAA,UAAAA,GAE3B,KAAK,YAAY,IACjB,KAAK,UAAUA,EAAQ,WAAW;AAClC,UAAMC,IAAOD,EAAQ;AAErB,eAAWE,KAAK,CAACD,EAAK,QAAQA,EAAK,OAAOA,EAAK,SAASA,EAAK,QAAQ;AACnE,UAAIC,KAAK,CAAC,KAAK,MAAM,IAAIA,CAAC,GAAG;AAC3B,cAAMC,IAAO,IAAIC,EAAMF,GAAGF,EAAQ,OAAOA,EAAQ,QAAQ,EAAE,eAAe,GAAA,CAAO;AACjF,aAAK,MAAM,IAAIE,GAAGC,CAAI,GACtB,KAAK,SAASA,CAAI;AAAA,MACpB;AAEF,SAAK,YAAYF,EAAK,QAAQ,IAAIG,EAAMH,EAAK,OAAOD,EAAQ,OAAOA,EAAQ,QAAQ,EAAE,eAAe,GAAA,CAAO,IAAI,MAC3G,KAAK,aAAW,KAAK,SAAS,KAAK,SAAS,GAChD,KAAK,UAAUA,EAAQ,WAAW,MAC9B,KAAK,WAAS,KAAK,SAAS,KAAK,OAAO,GAC5C,KAAK,gBAAA,GACL,KAAK,SAAS,WACd,KAAK,UAAU,IAAIK,EAAU,GAAG,GAAG,GAAG,CAAC,GACvC,KAAK,GAAG,eAAe,MAAM,KAAK,WAAW,IAAM,KAAK,QAAQ,CAAC,GACjE,KAAK,GAAG,cAAc,MAAM,KAAK,WAAW,IAAO,KAAK,QAAQ,CAAC,GACjE,KAAK,GAAG,eAAe,MAAM,KAAK,WAAW,KAAK,SAAS,EAAI,CAAC,GAChE,KAAK,GAAG,aAAa,MAAM,KAAK,WAAW,KAAK,SAAS,EAAK,CAAC,GAC/D,KAAK,GAAG,oBAAoB,MAAM,KAAK,WAAW,IAAO,EAAK,CAAC,GAC/D,KAAK,GAAG,iBAAiB,MAAM,KAAK,WAAW,IAAO,EAAK,CAAC,GAE5D,KAAK,GAAG,cAAc,MAAM;AAC1B,MAAKV,EAAc,IAAI,UAAQ,MAAA;AAAA,IACjC,CAAC,GACD,KAAK,OAAOK,EAAQ,OAAOA,EAAQ,MAAM;AAAA,EAC3C;AAAA,EA/B6B;AAAA,EAP7B;AAAA,EACiB,4BAAY,IAAA;AAAA,EACZ;AAAA,EACA;AAAA,EACT,UAAU;AAAA,EACV,WAAW;AAAA,EAmCnB,IAAI,QAAqB;AACvB,WAAK,KAAK,UACN,KAAK,WAAiB,YACnB,KAAK,UAAU,UAAU,WAFN;AAAA,EAG5B;AAAA;AAAA,EAGA,QAAc;AACZ,IAAI,KAAK,WAAS,KAAK,UAAA;AAAA,EACzB;AAAA,EAES,WAAiB;AACxB,SAAK,MAAA;AAAA,EACP;AAAA,EAEmB,aAAaM,GAAqB;AACnD,IAAIA,MAAU,KAAK,WAAS,KAAK,OAAA;AAAA,EACnC;AAAA,EAEQ,WAAWC,GAAkBC,GAAyB;AAC5D,IAAID,MAAY,KAAK,WAAWC,MAAa,KAAK,aAClD,KAAK,UAAUD,GACf,KAAK,WAAWC,GAChB,KAAK,OAAA;AAAA,EACP;AAAA,EAEU,SAAe;AACvB,UAAMP,IAAO,KAAK,QAAQ,MACpBQ,IAAQ,KAAK,OACbC,KAAUD,MAAU,aAAaR,EAAK,WAAWQ,MAAU,YAAaR,EAAK,WAAWA,EAAK,QAASQ,MAAU,UAAUR,EAAK,QAAQ,WAAcA,EAAK;AAChK,eAAW,CAACC,GAAGC,CAAI,KAAK,KAAK;AAC3B,MAAAA,EAAK,UAAUD,MAAMQ,IACjBP,EAAK,aAAa,KAAK,QAAQA,EAAK,cAAc,KAAK,SAAMA,EAAK,OAAO,KAAK,MAAM,KAAK,IAAI;AAUnG,QARI,KAAK,cACP,KAAK,UAAU,UAAU,KAAK,WAAW,KAAK,UAC1C,KAAK,UAAU,aAAa,KAAK,QAAQ,KAAK,UAAU,cAAc,KAAK,cAAW,UAAU,OAAO,KAAK,MAAM,KAAK,IAAI,IAE7H,KAAK,mBAAmBE,MAC1B,KAAK,QAAQ,QAAQ,KAAK,MAC1B,KAAK,QAAQ,SAAS,KAAK,OAEzB,KAAK,SAAS;AAChB,YAAMH,IAAIS,EAAO,KAAK,OAAO,GACvBC,IAAOH,MAAU,YAAaR,EAAK,eAAe,IAAK,GACvDY,IAAO,KAAK,MAAM,gBAAgB,IAClCC,KAAK,KAAK,OAAOZ,EAAE,SAAS,GAC5Ba,KAAK,KAAK,OAAOb,EAAE,UAAU,IAAIU;AACvC,WAAK,QAAQ,SAAS,IAAIC,IAAO,KAAK,MAAMC,CAAC,IAAIA,GAAGD,IAAO,KAAK,MAAME,CAAC,IAAIA,CAAC;AAAA,IAC9E;AAEA,SAAK,QAAQN,MAAU,cAAc,CAACR,EAAK,WAAW,MAAM;AAAA,EAC9D;AACF;"}
|
package/dist/ui/Dialog.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Dialog.js","sources":["../../src/ui/Dialog.ts"],"sourcesContent":["import { Container, Rectangle, Sprite, Texture } from 'pixi.js';\nimport { fitToViewport } from './layout/fitToViewport';\nimport type { Size } from './layout/geometry';\nimport { Panel, type PanelSkin } from './Panel';\nimport type { UiModal, UiRoot } from './UiRoot';\nimport { sizeOf, Widget } from './Widget';\n\nexport interface DialogOptions {\n width: number;\n /**\n * The window's height. Left out, it is the content's height plus the padding above and below, measured again\n * whenever the dialog is laid out (`UiRoot.layout()` after the content changed size).\n */\n height?: number;\n frame: PanelSkin;\n /** The body: a `Stack` of text and buttons, anything. Laid `padding` in from the frame. */\n content: Container;\n padding?: number;\n /** The screen behind it is dimmed by this (default black at 0.5); every press on it is eaten. */\n dimColor?: number;\n dimAlpha?: number;\n /** Cancel (Esc, the pad's B) or a press outside closes it (default true). */\n dismissable?: boolean;\n /** Kept clear of the screen's edges (default 8). */\n margin?: number;\n onClose?: () => void;\n}\n\n/**\n * A modal window: the world behind it dimmed and deaf to presses, keyboard and gamepad focus trapped inside it,\n * centred and kept inside the screen however small it gets (shrunk, never past its whole scale under pixel art).\n */\nexport class Dialog extends Widget implements UiModal {\n onClose: (() => void) | null;\n private readonly dim = new Sprite(Texture.WHITE);\n private readonly window = new Container();\n private readonly frame: Panel;\n private root: UiRoot | null = null;\n\n constructor(private readonly options: DialogOptions) {\n super();\n this.onClose = options.onClose ?? null;\n this.dim.tint = options.dimColor ?? 0x000000;\n this.dim.alpha = options.dimAlpha ?? 0.5;\n this.dim.eventMode = 'static';\n this.dim.on('pointertap', () => {\n if (options.dismissable ?? true) this.close();\n });\n const height = this.windowHeight();\n this.frame = new Panel(options.frame, options.width, height);\n this.window.addChild(this.frame, options.content);\n this.addChild(this.dim, this.window);\n // the dim is the dialog's own: every press on it is the UI's\n this.makeInteractive();\n this.boxW = options.width;\n this.boxH = height;\n }\n\n /** The window's height: the one given, or the content's with its padding. */\n private windowHeight(): number {\n if (this.options.height !== undefined) return this.options.height;\n return sizeOf(this.options.content).height + 2 * (this.options.padding ?? 8);\n }\n\n get isOpen(): boolean {\n return this.root !== null;\n }\n\n open(root: UiRoot): this {\n if (this.root) return this;\n this.root = root;\n root.openModal(this);\n return this;\n }\n\n close(): void {\n const root = this.root;\n if (!root) return;\n this.root = null;\n root.closeModal(this);\n this.onClose?.();\n }\n\n cancel(): boolean {\n if (!(this.options.dismissable ?? true)) return false;\n this.close();\n return true;\n }\n\n layoutFor(viewport: Size): void {\n this.dim.setSize(viewport.width, viewport.height);\n this.hitArea = new Rectangle(0, 0, viewport.width, viewport.height);\n const pixel = this.host?.pixelPerfect ?? false;\n const height = this.windowHeight();\n this.boxH = height;\n const fit = fitToViewport({ width: this.options.width, height }, viewport, {\n margin: this.options.margin ?? 8,\n centre: true,\n minScale: pixel ? 1 : 0.5,\n snap: pixel,\n });\n this.window.position.set(fit.x, fit.y);\n this.window.scale.set(fit.scale);\n this.frame.resize(fit.width, fit.height);\n const pad = this.options.padding ?? 8;\n const s = sizeOf(this.options.content);\n const x = (fit.width - s.width) / 2;\n this.options.content.position.set(pixel ? Math.round(Math.max(pad, x)) : Math.max(pad, x), pad);\n }\n\n protected redraw(): void {}\n}\n"],"names":["Dialog","Widget","options","height","Panel","Sprite","Texture","Container","sizeOf","root","viewport","Rectangle","pixel","fit","fitToViewport","pad","s","x"],"mappings":";;;;AAgCO,MAAMA,UAAeC,EAA0B;AAAA,EAOpD,YAA6BC,GAAwB;AACnD,UAAA,GAD2B,KAAA,UAAAA,GAE3B,KAAK,UAAUA,EAAQ,WAAW,MAClC,KAAK,IAAI,OAAOA,EAAQ,YAAY,GACpC,KAAK,IAAI,QAAQA,EAAQ,YAAY,KACrC,KAAK,IAAI,YAAY,UACrB,KAAK,IAAI,GAAG,cAAc,MAAM;AAC9B,OAAIA,EAAQ,eAAe,OAAM,KAAK,MAAA;AAAA,IACxC,CAAC;AACD,UAAMC,IAAS,KAAK,aAAA;AACpB,SAAK,QAAQ,IAAIC,EAAMF,EAAQ,OAAOA,EAAQ,OAAOC,CAAM,GAC3D,KAAK,OAAO,SAAS,KAAK,OAAOD,EAAQ,OAAO,GAChD,KAAK,SAAS,KAAK,KAAK,KAAK,MAAM,GAEnC,KAAK,gBAAA,GACL,KAAK,OAAOA,EAAQ,OACpB,KAAK,OAAOC;AAAA,EACd;AAAA,EAjB6B;AAAA,EAN7B;AAAA,EACiB,MAAM,IAAIE,EAAOC,EAAQ,KAAK;AAAA,EAC9B,SAAS,IAAIC,EAAA;AAAA,EACb;AAAA,EACT,OAAsB;AAAA;AAAA,EAsBtB,eAAuB;AAC7B,WAAI,KAAK,QAAQ,WAAW,SAAkB,KAAK,QAAQ,SACpDC,EAAO,KAAK,QAAQ,OAAO,EAAE,SAAS,KAAK,KAAK,QAAQ,WAAW;AAAA,EAC5E;AAAA,EAEA,IAAI,SAAkB;AACpB,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA,EAEA,KAAKC,GAAoB;AACvB,WAAI,KAAK,OAAa,QACtB,KAAK,OAAOA,GACZA,EAAK,UAAU,IAAI,GACZ;AAAA,EACT;AAAA,EAEA,QAAc;AACZ,UAAMA,IAAO,KAAK;AAClB,IAAKA,MACL,KAAK,OAAO,MACZA,EAAK,WAAW,IAAI,GACpB,KAAK,UAAA;AAAA,EACP;AAAA,EAEA,SAAkB;AAChB,WAAM,KAAK,QAAQ,eAAe,MAClC,KAAK,MAAA,GACE,MAFyC;AAAA,EAGlD;AAAA,EAEA,UAAUC,GAAsB;AAC9B,SAAK,IAAI,QAAQA,EAAS,OAAOA,EAAS,MAAM,GAChD,KAAK,UAAU,IAAIC,EAAU,GAAG,GAAGD,EAAS,OAAOA,EAAS,MAAM;AAClE,UAAME,IAAQ,KAAK,MAAM,gBAAgB,IACnCT,IAAS,KAAK,aAAA;AACpB,SAAK,OAAOA;AACZ,UAAMU,IAAMC,EAAc,EAAE,OAAO,KAAK,QAAQ,OAAO,QAAAX,EAAA,GAAUO,GAAU;AAAA,MACzE,QAAQ,KAAK,QAAQ,UAAU;AAAA,MAC/B,QAAQ;AAAA,MACR,UAAUE,IAAQ,IAAI;AAAA,MACtB,MAAMA;AAAA,IAAA,CACP;AACD,SAAK,OAAO,SAAS,IAAIC,EAAI,GAAGA,EAAI,CAAC,GACrC,KAAK,OAAO,MAAM,IAAIA,EAAI,KAAK,GAC/B,KAAK,MAAM,OAAOA,EAAI,OAAOA,EAAI,MAAM;AACvC,UAAME,IAAM,KAAK,QAAQ,WAAW,GAC9BC,IAAIR,EAAO,KAAK,QAAQ,OAAO,GAC/BS,KAAKJ,EAAI,QAAQG,EAAE,SAAS;AAClC,SAAK,QAAQ,QAAQ,SAAS,IAAIJ,IAAQ,KAAK,MAAM,KAAK,IAAIG,GAAKE,CAAC,CAAC,IAAI,KAAK,IAAIF,GAAKE,CAAC,GAAGF,CAAG;AAAA,EAChG;AAAA,EAEU,SAAe;AAAA,EAAC;AAC5B;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"FocusManager.js","sources":["../../src/ui/FocusManager.ts"],"sourcesContent":["import type { Container } from 'pixi.js';\nimport { firstFocus, linearFocus, nextFocus, type NavDirection } from './layout/focusNav';\nimport type { Rect } from './layout/geometry';\nimport { Widget } from './Widget';\n\n/** Anything that answers `pressed(action)`: the engine's `Actions`, or a game's own input. */\nexport interface NavInput {\n pressed(action: string): boolean;\n}\n\n/** The action names the UI reads; a game binds them in its `Actions` (`ui_up: ['ArrowUp', 'pad:up', 'pad:ly-']`…). */\nexport interface NavBindings {\n up: string;\n down: string;\n left: string;\n right: string;\n accept: string;\n cancel: string;\n /** Tab order, for a keyboard. */\n next: string;\n prev: string;\n}\n\nexport const DEFAULT_NAV: NavBindings = {\n up: 'ui_up',\n down: 'ui_down',\n left: 'ui_left',\n right: 'ui_right',\n accept: 'ui_accept',\n cancel: 'ui_cancel',\n next: 'ui_next',\n prev: 'ui_prev',\n};\n\n/** A container focus is kept inside (a dialog's trap), and what Cancel does there. */\ninterface Scope {\n container: Container;\n onCancel: (() => boolean) | null;\n}\n\nconst DIRECTIONS: readonly NavDirection[] = ['up', 'down', 'left', 'right'];\n\n/**\n * Keyboard and gamepad navigation between widgets: directions move spatially (to the nearest widget that way), Tab\n * steps in tree order, Accept activates, Cancel asks the innermost scope. A modal pushes a scope, and focus cannot\n * leave it until it is popped (the focus trap). The list of focusable widgets is collected only when stale.\n */\nexport class FocusManager {\n private readonly scopes: Scope[] = [];\n private list: Widget[] | null = null;\n private current: Widget | null = null;\n\n constructor(\n root: Container,\n readonly bindings: NavBindings = DEFAULT_NAV,\n ) {\n this.scopes.push({ container: root, onCancel: null });\n }\n\n get focused(): Widget | null {\n return this.current;\n }\n\n /** The focusable widgets changed (added, removed, shown, hidden, enabled): collect them again on the next move. */\n invalidate(): void {\n this.list = null;\n if (this.current && (this.current.destroyed || !this.current.canFocus() || !this.inScope(this.current))) this.focus(null);\n }\n\n /** Traps focus inside `container` until `popScope`; `onCancel` answers Cancel there (true when it used it). */\n pushScope(container: Container, onCancel: (() => boolean) | null = null): void {\n this.scopes.push({ container, onCancel });\n this.list = null;\n this.focus(null);\n }\n\n popScope(container: Container): void {\n const i = this.scopes.findIndex((s) => s.container === container);\n if (i <= 0) return;\n this.scopes.splice(i);\n this.list = null;\n this.focus(null);\n }\n\n /** The innermost scope's container (the open dialog, or the root). */\n get scope(): Container {\n return this.scopes[this.scopes.length - 1]!.container;\n }\n\n focus(w: Widget | null): void {\n if (w === this.current) return;\n this.current?.setFocused(false);\n this.current = w;\n if (!w) return;\n w.setFocused(true);\n // a scroll view holding it brings it into view\n for (let p = w.parent; p; p = p.parent) (p as { reveal?: (c: Widget) => void }).reveal?.(w);\n }\n\n /** Moves focus one way; the first press with nothing focused lands on the top-left widget. */\n move(dir: NavDirection): boolean {\n const list = this.widgets();\n if (list.length === 0) return false;\n const rects = list.map(rectOf);\n const i = this.current ? list.indexOf(this.current) : -1;\n const next = i < 0 ? firstFocus(rects) : nextFocus(rects, i, dir);\n if (next < 0 || next === i) return false;\n this.focus(list[next]!);\n return true;\n }\n\n /** Tab (1) or Shift+Tab (−1), in tree order, wrapping. */\n step(step: 1 | -1): boolean {\n const list = this.widgets();\n if (list.length === 0) return false;\n const i = this.current ? list.indexOf(this.current) : -1;\n this.focus(list[linearFocus(list.length, i, step)]!);\n return true;\n }\n\n /**\n * Reads one step of input; call it between the game's `actions.poll()` and `endStep()`. True when the UI used a\n * press, so the game leaves it alone.\n */\n handle(input: NavInput): boolean {\n const b = this.bindings;\n let used = false;\n for (const dir of DIRECTIONS) {\n if (!input.pressed(b[dir])) continue;\n // a focused widget may use the direction itself (a slider steps on left/right)\n used = (this.current?.navigate(dir) ?? false) || this.move(dir) || used;\n }\n if (input.pressed(b.next)) used = this.step(1) || used;\n if (input.pressed(b.prev)) used = this.step(-1) || used;\n if (input.pressed(b.accept) && this.current?.canFocus()) {\n this.current.activate();\n used = true;\n }\n if (input.pressed(b.cancel)) {\n const scope = this.scopes[this.scopes.length - 1]!;\n if (scope.onCancel?.()) used = true;\n else if (this.current) {\n this.focus(null);\n used = true;\n }\n }\n return used;\n }\n\n private inScope(w: Container): boolean {\n for (let p: Container | null = w; p; p = p.parent) if (p === this.scope) return true;\n return false;\n }\n\n private widgets(): Widget[] {\n if (!this.list) {\n const out: Widget[] = [];\n collect(this.scope, out);\n this.list = out;\n }\n return this.list.filter((w) => !w.destroyed && w.canFocus());\n }\n}\n\nfunction collect(c: Container, out: Widget[]): void {\n if (!c.visible) return;\n if (c instanceof Widget && c.focusable) out.push(c);\n for (const child of c.children) collect(child, out);\n}\n\n/** A widget's box on screen: comparable across parents, whatever scrolled or scaled them. */\nfunction rectOf(w: Widget): Rect {\n const b = w.getBounds();\n return { x: b.x, y: b.y, width: b.width, height: b.height };\n}\n"],"names":["DEFAULT_NAV","DIRECTIONS","FocusManager","root","bindings","container","onCancel","i","w","p","dir","list","rects","rectOf","next","firstFocus","nextFocus","step","linearFocus","input","b","used","out","collect","c","Widget","child"],"mappings":"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"}
|
package/dist/ui/Label.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Label.js","sources":["../../src/ui/Label.ts"],"sourcesContent":["import { BitmapLabel } from '../core/BitmapLabel';\nimport { Text, type TextStyle } from '../core/Text';\nimport { Widget } from './Widget';\n\n/** What a label prints with: a canvas `Text` (any font, rich wrapping) or a `BitmapLabel` (cheap to change). */\nexport type LabelText = Text | BitmapLabel;\n\n/**\n * A line (or block) of text with a layout box measured from what it prints. A HUD number rewritten every frame\n * should be a `BitmapLabel`: a `Text` repaints and uploads its canvas on every change.\n */\nexport class Label extends Widget {\n private last = '';\n\n constructor(readonly text: LabelText) {\n super();\n text.anchor.set(0, 0);\n text.position.set(0, 0);\n this.addChild(text);\n this.last = text.text;\n this.measure();\n }\n\n /** A label printed with a canvas `Text`. */\n static text(value: string, style: TextStyle): Label {\n return new Label(new Text(0, 0, value, style));\n }\n\n /** A label printed from a bitmap font (see `installLabelFont`; `glyphs` beyond ASCII it will print). */\n static bitmap(value: string, fontFamily: string, size: number, color: string, stroke = '#000000', strokeWidth = 0, resolution = 1, glyphs = ''): Label {\n return new Label(new BitmapLabel(0, 0, value, fontFamily, size, color, stroke, strokeWidth, resolution, glyphs));\n }\n\n get value(): string {\n return this.last;\n }\n\n /** Changes the text; the same string again costs nothing (a HUD may set it every frame). */\n setText(value: string): this {\n if (value === this.last) return this;\n this.last = value;\n this.text.text = value;\n this.measure();\n return this;\n }\n\n private measure(): void {\n const w = this.boxW;\n const h = this.boxH;\n // rounded up: a box that loses the last half pixel of a glyph clips it in a tight layout\n this.boxW = Math.ceil(this.text.width);\n this.boxH = Math.ceil(this.text.height);\n if (this.boxW !== w || this.boxH !== h) this.notifyResized();\n }\n\n protected redraw(): void {\n this.alpha = this.enabled ? 1 : 0.5;\n }\n}\n"],"names":["Label","Widget","text","value","style","Text","fontFamily","size","color","stroke","strokeWidth","resolution","glyphs","BitmapLabel","w","h"],"mappings":";;;AAWO,MAAMA,UAAcC,EAAO;AAAA,EAGhC,YAAqBC,GAAiB;AACpC,UAAA,GADmB,KAAA,OAAAA,GAEnBA,EAAK,OAAO,IAAI,GAAG,CAAC,GACpBA,EAAK,SAAS,IAAI,GAAG,CAAC,GACtB,KAAK,SAASA,CAAI,GAClB,KAAK,OAAOA,EAAK,MACjB,KAAK,QAAA;AAAA,EACP;AAAA,EAPqB;AAAA,EAFb,OAAO;AAAA;AAAA,EAYf,OAAO,KAAKC,GAAeC,GAAyB;AAClD,WAAO,IAAIJ,EAAM,IAAIK,EAAK,GAAG,GAAGF,GAAOC,CAAK,CAAC;AAAA,EAC/C;AAAA;AAAA,EAGA,OAAO,OAAOD,GAAeG,GAAoBC,GAAcC,GAAeC,IAAS,WAAWC,IAAc,GAAGC,IAAa,GAAGC,IAAS,IAAW;AACrJ,WAAO,IAAIZ,EAAM,IAAIa,EAAY,GAAG,GAAGV,GAAOG,GAAYC,GAAMC,GAAOC,GAAQC,GAAaC,GAAYC,CAAM,CAAC;AAAA,EACjH;AAAA,EAEA,IAAI,QAAgB;AAClB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,QAAQT,GAAqB;AAC3B,WAAIA,MAAU,KAAK,OAAa,QAChC,KAAK,OAAOA,GACZ,KAAK,KAAK,OAAOA,GACjB,KAAK,QAAA,GACE;AAAA,EACT;AAAA,EAEQ,UAAgB;AACtB,UAAMW,IAAI,KAAK,MACTC,IAAI,KAAK;AAEf,SAAK,OAAO,KAAK,KAAK,KAAK,KAAK,KAAK,GACrC,KAAK,OAAO,KAAK,KAAK,KAAK,KAAK,MAAM,IAClC,KAAK,SAASD,KAAK,KAAK,SAASC,WAAQ,cAAA;AAAA,EAC/C;AAAA,EAEU,SAAe;AACvB,SAAK,QAAQ,KAAK,UAAU,IAAI;AAAA,EAClC;AACF;"}
|
package/dist/ui/List.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"List.js","sources":["../../src/ui/List.ts"],"sourcesContent":["import type { Container } from 'pixi.js';\nimport { stackLayout } from './layout/stack';\nimport { ScrollView, type ScrollViewOptions } from './ScrollView';\nimport { sizeOf, Widget } from './Widget';\n\nexport interface ListOptions extends ScrollViewOptions {\n /** Between rows (default 0). */\n gap?: number;\n /** Inside the list's edge (default 0). */\n padding?: number;\n /** Rows as wide as the list, less the padding (default true). */\n stretch?: boolean;\n}\n\n/** A vertical list of rows in a scroll view: rows stacked with a gap, clipped, scrolled, followed by the focus. */\nexport class List extends ScrollView {\n private readonly rows: Container[] = [];\n\n constructor(private readonly listOptions: ListOptions) {\n super(listOptions);\n }\n\n get items(): readonly Container[] {\n return this.rows;\n }\n\n add(...rows: Container[]): this {\n for (const r of rows) {\n this.rows.push(r);\n this.content.addChild(r);\n }\n return this.relayout();\n }\n\n remove(row: Container, destroy = false): this {\n const i = this.rows.indexOf(row);\n if (i >= 0) this.rows.splice(i, 1);\n if (row.parent === this.content) this.content.removeChild(row);\n if (destroy) row.destroy({ children: true });\n return this.relayout();\n }\n\n clear(destroy = true): this {\n for (const r of this.rows.splice(0)) {\n this.content.removeChild(r);\n if (destroy) r.destroy({ children: true });\n }\n return this.relayout();\n }\n\n /** Lays the rows out again (after a row changed size). */\n relayout(): this {\n const pad = this.listOptions.padding ?? 0;\n const stretch = this.listOptions.stretch ?? true;\n const snap = this.host?.pixelPerfect ?? false;\n const result = stackLayout(this.rows.map(sizeOf), {\n gap: this.listOptions.gap ?? 0,\n padding: pad,\n align: stretch ? 'stretch' : 'start',\n crossSize: this.boxWidth - 2 * pad,\n snap,\n });\n this.rows.forEach((r, i) => {\n const rect = result.items[i]!;\n r.position.set(rect.x, rect.y);\n if (stretch && r instanceof Widget && r.boxWidth !== rect.width) r.resize(rect.width, r.boxHeight);\n });\n this.setContentHeight(result.height);\n this.host?.invalidateFocus();\n return this;\n }\n}\n"],"names":["List","ScrollView","listOptions","rows","r","row","destroy","i","pad","stretch","snap","result","stackLayout","sizeOf","rect","Widget"],"mappings":";;;AAeO,MAAMA,UAAaC,EAAW;AAAA,EAGnC,YAA6BC,GAA0B;AACrD,UAAMA,CAAW,GADU,KAAA,cAAAA;AAAA,EAE7B;AAAA,EAF6B;AAAA,EAFZ,OAAoB,CAAA;AAAA,EAMrC,IAAI,QAA8B;AAChC,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,OAAOC,GAAyB;AAC9B,eAAWC,KAAKD;AACd,WAAK,KAAK,KAAKC,CAAC,GAChB,KAAK,QAAQ,SAASA,CAAC;AAEzB,WAAO,KAAK,SAAA;AAAA,EACd;AAAA,EAEA,OAAOC,GAAgBC,IAAU,IAAa;AAC5C,UAAMC,IAAI,KAAK,KAAK,QAAQF,CAAG;AAC/B,WAAIE,KAAK,KAAG,KAAK,KAAK,OAAOA,GAAG,CAAC,GAC7BF,EAAI,WAAW,KAAK,WAAS,KAAK,QAAQ,YAAYA,CAAG,GACzDC,KAASD,EAAI,QAAQ,EAAE,UAAU,IAAM,GACpC,KAAK,SAAA;AAAA,EACd;AAAA,EAEA,MAAMC,IAAU,IAAY;AAC1B,eAAWF,KAAK,KAAK,KAAK,OAAO,CAAC;AAChC,WAAK,QAAQ,YAAYA,CAAC,GACtBE,KAASF,EAAE,QAAQ,EAAE,UAAU,IAAM;AAE3C,WAAO,KAAK,SAAA;AAAA,EACd;AAAA;AAAA,EAGA,WAAiB;AACf,UAAMI,IAAM,KAAK,YAAY,WAAW,GAClCC,IAAU,KAAK,YAAY,WAAW,IACtCC,IAAO,KAAK,MAAM,gBAAgB,IAClCC,IAASC,EAAY,KAAK,KAAK,IAAIC,CAAM,GAAG;AAAA,MAChD,KAAK,KAAK,YAAY,OAAO;AAAA,MAC7B,SAASL;AAAA,MACT,OAAOC,IAAU,YAAY;AAAA,MAC7B,WAAW,KAAK,WAAW,IAAID;AAAA,MAC/B,MAAAE;AAAA,IAAA,CACD;AACD,gBAAK,KAAK,QAAQ,CAACN,GAAGG,MAAM;AAC1B,YAAMO,IAAOH,EAAO,MAAMJ,CAAC;AAC3B,MAAAH,EAAE,SAAS,IAAIU,EAAK,GAAGA,EAAK,CAAC,GACzBL,KAAWL,aAAaW,KAAUX,EAAE,aAAaU,EAAK,SAAOV,EAAE,OAAOU,EAAK,OAAOV,EAAE,SAAS;AAAA,IACnG,CAAC,GACD,KAAK,iBAAiBO,EAAO,MAAM,GACnC,KAAK,MAAM,gBAAA,GACJ;AAAA,EACT;AACF;"}
|
package/dist/ui/Panel.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Panel.js","sources":["../../src/ui/Panel.ts"],"sourcesContent":["import { Graphics, NineSliceSprite, Rectangle, type Texture } from 'pixi.js';\nimport type { Insets } from './layout/geometry';\nimport { Widget } from './Widget';\n\n/** A 9-slice texture: the corners stay as drawn, the edges stretch one way, the middle both. */\nexport interface TextureSkin {\n texture: Texture;\n /** Corner sizes in texture pixels. */\n insets: Insets;\n}\n\n/** A plain box: fill, and an optional border drawn inside the edge. */\nexport interface ColourSkin {\n color: number;\n /** Opacity of fill and border alike (default 1); `fillAlpha` and `borderAlpha` override it for one of them. */\n alpha?: number;\n /** Opacity of the fill alone: 0 draws a bare ring (a focus mark over a face that should still show). */\n fillAlpha?: number;\n /** Opacity of the border alone. */\n borderAlpha?: number;\n border?: number;\n borderColor?: number;\n}\n\nexport type PanelSkin = TextureSkin | ColourSkin;\n\nexport interface PanelOptions {\n /** Presses on it are the UI's and never reach the world under it (default true). */\n blocksPointer?: boolean;\n}\n\n/**\n * A framed box: a 9-slice of any texture (a window frame, a button face), or a colour box when a game has no art\n * yet. The background of windows, buttons, bars and dialogs.\n */\nexport class Panel extends Widget {\n private readonly slice: NineSliceSprite | null = null;\n private readonly box: Graphics | null = null;\n\n constructor(\n readonly skin: PanelSkin,\n width: number,\n height: number,\n options: PanelOptions = {},\n ) {\n super();\n if ('texture' in skin) {\n const { left, top, right, bottom } = skin.insets;\n this.slice = new NineSliceSprite({ texture: skin.texture, leftWidth: left, topHeight: top, rightWidth: right, bottomHeight: bottom });\n this.addChild(this.slice);\n } else {\n this.box = new Graphics();\n this.addChild(this.box);\n }\n if (options.blocksPointer ?? true) {\n this.makeInteractive();\n this.hitArea = new Rectangle(0, 0, 0, 0);\n }\n this.resize(width, height);\n }\n\n protected redraw(): void {\n const w = this.boxW;\n const h = this.boxH;\n if (this.hitArea instanceof Rectangle) {\n this.hitArea.width = w;\n this.hitArea.height = h;\n }\n if (this.slice) {\n // pixel art: the frame's corners land on whole pixels\n this.slice.roundPixels = this.host?.pixelPerfect ?? false;\n this.slice.width = w;\n this.slice.height = h;\n return;\n }\n const skin = this.skin as ColourSkin;\n const g = this.box!;\n g.clear();\n const b = skin.border ?? 0;\n const fillAlpha = skin.fillAlpha ?? skin.alpha ?? 1;\n const borderAlpha = skin.borderAlpha ?? skin.alpha ?? 1;\n if (w <= 0 || h <= 0) return;\n if (b > 0) {\n // the border as four strips round the fill, never a box under it: a see-through fill would show it through\n const bc = { color: skin.borderColor ?? 0xffffff, alpha: borderAlpha };\n if (w <= 2 * b || h <= 2 * b) {\n g.rect(0, 0, w, h).fill(bc);\n return;\n }\n g.rect(0, 0, w, b).fill(bc);\n g.rect(0, h - b, w, b).fill(bc);\n g.rect(0, b, b, h - 2 * b).fill(bc);\n g.rect(w - b, b, b, h - 2 * b).fill(bc);\n if (fillAlpha > 0) g.rect(b, b, w - 2 * b, h - 2 * b).fill({ color: skin.color, alpha: fillAlpha });\n } else if (fillAlpha > 0) {\n g.rect(0, 0, w, h).fill({ color: skin.color, alpha: fillAlpha });\n }\n }\n}\n"],"names":["Panel","Widget","skin","width","height","options","left","top","right","bottom","NineSliceSprite","Graphics","Rectangle","w","h","g","b","fillAlpha","borderAlpha","bc"],"mappings":";;AAmCO,MAAMA,UAAcC,EAAO;AAAA,EAIhC,YACWC,GACTC,GACAC,GACAC,IAAwB,CAAA,GACxB;AAEA,QADA,MAAA,GALS,KAAA,OAAAH,GAML,aAAaA,GAAM;AACrB,YAAM,EAAE,MAAAI,GAAM,KAAAC,GAAK,OAAAC,GAAO,QAAAC,EAAA,IAAWP,EAAK;AAC1C,WAAK,QAAQ,IAAIQ,EAAgB,EAAE,SAASR,EAAK,SAAS,WAAWI,GAAM,WAAWC,GAAK,YAAYC,GAAO,cAAcC,GAAQ,GACpI,KAAK,SAAS,KAAK,KAAK;AAAA,IAC1B;AACE,WAAK,MAAM,IAAIE,EAAA,GACf,KAAK,SAAS,KAAK,GAAG;AAExB,KAAIN,EAAQ,iBAAiB,QAC3B,KAAK,gBAAA,GACL,KAAK,UAAU,IAAIO,EAAU,GAAG,GAAG,GAAG,CAAC,IAEzC,KAAK,OAAOT,GAAOC,CAAM;AAAA,EAC3B;AAAA,EAnBW;AAAA,EAJM,QAAgC;AAAA,EAChC,MAAuB;AAAA,EAwB9B,SAAe;AACvB,UAAMS,IAAI,KAAK,MACTC,IAAI,KAAK;AAKf,QAJI,KAAK,mBAAmBF,MAC1B,KAAK,QAAQ,QAAQC,GACrB,KAAK,QAAQ,SAASC,IAEpB,KAAK,OAAO;AAEd,WAAK,MAAM,cAAc,KAAK,MAAM,gBAAgB,IACpD,KAAK,MAAM,QAAQD,GACnB,KAAK,MAAM,SAASC;AACpB;AAAA,IACF;AACA,UAAMZ,IAAO,KAAK,MACZa,IAAI,KAAK;AACf,IAAAA,EAAE,MAAA;AACF,UAAMC,IAAId,EAAK,UAAU,GACnBe,IAAYf,EAAK,aAAaA,EAAK,SAAS,GAC5CgB,IAAchB,EAAK,eAAeA,EAAK,SAAS;AACtD,QAAI,EAAAW,KAAK,KAAKC,KAAK;AACnB,UAAIE,IAAI,GAAG;AAET,cAAMG,IAAK,EAAE,OAAOjB,EAAK,eAAe,UAAU,OAAOgB,EAAA;AACzD,YAAIL,KAAK,IAAIG,KAAKF,KAAK,IAAIE,GAAG;AAC5B,UAAAD,EAAE,KAAK,GAAG,GAAGF,GAAGC,CAAC,EAAE,KAAKK,CAAE;AAC1B;AAAA,QACF;AACA,QAAAJ,EAAE,KAAK,GAAG,GAAGF,GAAGG,CAAC,EAAE,KAAKG,CAAE,GAC1BJ,EAAE,KAAK,GAAGD,IAAIE,GAAGH,GAAGG,CAAC,EAAE,KAAKG,CAAE,GAC9BJ,EAAE,KAAK,GAAGC,GAAGA,GAAGF,IAAI,IAAIE,CAAC,EAAE,KAAKG,CAAE,GAClCJ,EAAE,KAAKF,IAAIG,GAAGA,GAAGA,GAAGF,IAAI,IAAIE,CAAC,EAAE,KAAKG,CAAE,GAClCF,IAAY,KAAGF,EAAE,KAAKC,GAAGA,GAAGH,IAAI,IAAIG,GAAGF,IAAI,IAAIE,CAAC,EAAE,KAAK,EAAE,OAAOd,EAAK,OAAO,OAAOe,GAAW;AAAA,MACpG,MAAA,CAAWA,IAAY,KACrBF,EAAE,KAAK,GAAG,GAAGF,GAAGC,CAAC,EAAE,KAAK,EAAE,OAAOZ,EAAK,OAAO,OAAOe,GAAW;AAAA,EAEnE;AACF;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ScrollView.js","sources":["../../src/ui/ScrollView.ts"],"sourcesContent":["import { Container, Graphics, Rectangle, Sprite, Texture, type FederatedPointerEvent, type FederatedWheelEvent } from 'pixi.js';\nimport { clampScroll, maxScroll, scrollIntoView, stepFling } from './layout/scroll';\nimport type { UiTicking } from './UiHost';\nimport { Widget } from './Widget';\n\nexport interface ScrollViewOptions {\n width: number;\n height: number;\n /** Pixels a wheel notch scrolls at most (default 48). */\n wheelStep?: number;\n /** How far a press must move before it is a drag instead of a tap (default 6 px). */\n dragSlop?: number;\n /** The scroll bar's colour; null hides it (default white at 0.4 alpha). */\n barColor?: number | null;\n barWidth?: number;\n}\n\n/**\n * A vertical scroll view: `content` is clipped to the box and scrolled by the wheel, by dragging (with a fling) and\n * to whatever the keyboard focuses. A press that turned into a drag is not a tap on the button under it.\n */\nexport class ScrollView extends Widget implements UiTicking {\n readonly content = new Container();\n /** True from the moment a press became a drag until the next press: buttons inside ignore that tap. */\n suppressesTaps = false;\n private readonly clip = new Graphics();\n private readonly bar: Sprite | null;\n private offset = 0;\n private contentH = 0;\n private velocity = 0;\n private ticking = false;\n private clipW = -1;\n private clipH = -1;\n private dragFrom = 0;\n private dragOffset = 0;\n private lastY = 0;\n private lastT = 0;\n private readonly onMove = (e: FederatedPointerEvent) => this.dragMove(e);\n private readonly onUp = () => this.dragEnd();\n\n constructor(private readonly options: ScrollViewOptions) {\n super();\n this.addChild(this.content, this.clip);\n this.content.mask = this.clip;\n const color = options.barColor === undefined ? 0xffffff : options.barColor;\n this.bar = color === null ? null : new Sprite(Texture.WHITE);\n if (this.bar) {\n this.bar.tint = color as number;\n this.bar.alpha = 0.4;\n this.addChild(this.bar);\n }\n this.makeInteractive();\n this.hitArea = new Rectangle(0, 0, 0, 0);\n this.on('wheel', (e: FederatedWheelEvent) => {\n const step = options.wheelStep ?? 48;\n const dy = Math.max(-step, Math.min(step, e.deltaY));\n if (this.scrollTo(this.offset + dy)) e.stopPropagation();\n });\n this.on('pointerdown', (e) => this.dragStart(e));\n this.resize(options.width, options.height);\n }\n\n get scrollY(): number {\n return this.offset;\n }\n\n get contentHeight(): number {\n return this.contentH;\n }\n\n /** Sets how tall the content is (default: measured from it by `measure`). */\n setContentHeight(h: number): this {\n this.contentH = Math.max(0, h);\n this.scrollTo(this.offset);\n this.redraw();\n return this;\n }\n\n /** Measures the content's drawn height; call after filling it. */\n measure(): this {\n const b = this.content.getLocalBounds();\n return this.setContentHeight(b.maxY);\n }\n\n /** Scrolls to `y`, kept in range; true when it moved. */\n scrollTo(y: number): boolean {\n const next = clampScroll(y, this.contentH, this.boxH);\n if (next === this.offset) return false;\n this.offset = next;\n this.redraw();\n return true;\n }\n\n /** Brings a child into view (the focus manager calls it when a widget inside is focused). */\n reveal(child: Container): void {\n const top = this.content.toLocal(child.getGlobalPosition()).y;\n const h = child instanceof Widget ? child.boxHeight : child.getLocalBounds().maxY;\n this.velocity = 0;\n this.scrollTo(scrollIntoView(this.offset, top, h, this.boxH, this.contentH));\n }\n\n tick(dtMs: number): boolean {\n if (this.destroyed || this.velocity === 0) return (this.ticking = false);\n const moved = this.scrollTo(this.offset + (this.velocity * dtMs) / 1000);\n this.velocity = moved ? stepFling(this.velocity, dtMs) : 0;\n this.ticking = this.velocity !== 0;\n return this.ticking;\n }\n\n override destroy(): void {\n this.dragEnd();\n super.destroy({ children: true });\n }\n\n private dragStart(e: FederatedPointerEvent): void {\n this.suppressesTaps = false;\n this.velocity = 0;\n this.dragFrom = e.global.y;\n this.dragOffset = this.offset;\n this.lastY = e.global.y;\n this.lastT = performance.now();\n this.on('globalpointermove', this.onMove);\n this.on('pointerup', this.onUp);\n this.on('pointerupoutside', this.onUp);\n }\n\n private dragMove(e: FederatedPointerEvent): void {\n const y = e.global.y;\n if (!this.suppressesTaps && Math.abs(y - this.dragFrom) < (this.options.dragSlop ?? 6)) return;\n this.suppressesTaps = true;\n const now = performance.now();\n const dt = now - this.lastT;\n // the speed of the last move, for the fling when the finger lifts\n if (dt > 0) this.velocity = ((this.lastY - y) / dt) * 1000;\n this.lastY = y;\n this.lastT = now;\n this.scrollTo(this.dragOffset + (this.dragFrom - y));\n }\n\n private dragEnd(): void {\n this.off('globalpointermove', this.onMove);\n this.off('pointerup', this.onUp);\n this.off('pointerupoutside', this.onUp);\n // a finger held still before lifting does not fling\n if (performance.now() - this.lastT > 80) this.velocity = 0;\n if (this.velocity !== 0 && !this.ticking) {\n const host = this.host;\n if (host) {\n this.ticking = true;\n host.animate(this);\n } else this.velocity = 0;\n }\n }\n\n protected redraw(): void {\n const w = this.boxW;\n const h = this.boxH;\n // the mask is rebuilt only when the box changes, never on a scroll (a fling redraws every frame)\n if (w !== this.clipW || h !== this.clipH) {\n this.clipW = w;\n this.clipH = h;\n this.clip.clear().rect(0, 0, w, h).fill(0xffffff);\n }\n if (this.hitArea instanceof Rectangle) {\n this.hitArea.width = w;\n this.hitArea.height = h;\n }\n const snap = this.host?.pixelPerfect ?? false;\n this.content.y = snap ? -Math.round(this.offset) : -this.offset;\n if (this.bar) {\n const max = maxScroll(this.contentH, h);\n this.bar.visible = max > 0;\n if (max > 0) {\n const bw = this.options.barWidth ?? 2;\n const bh = Math.max(8, Math.round((h * h) / this.contentH));\n this.bar.setSize(bw, bh);\n this.bar.position.set(w - bw, Math.round((this.offset / max) * (h - bh)));\n }\n }\n }\n}\n"],"names":["ScrollView","Widget","options","color","Sprite","Texture","Rectangle","e","step","dy","Container","Graphics","h","b","y","next","clampScroll","child","top","scrollIntoView","dtMs","moved","stepFling","now","dt","host","w","snap","max","maxScroll","bw","bh"],"mappings":"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"}
|
package/dist/ui/Slider.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Slider.js","sources":["../../src/ui/Slider.ts"],"sourcesContent":["import { Rectangle, type FederatedPointerEvent } from 'pixi.js';\nimport type { NavDirection } from './layout/focusNav';\nimport { quantize, sliderFraction, sliderValueAt } from './layout/slider';\nimport { Panel, type PanelSkin } from './Panel';\nimport { Widget } from './Widget';\n\nexport interface SliderOptions {\n width: number;\n height: number;\n track: PanelSkin;\n /** The filled part left of the knob (optional). */\n fill?: PanelSkin;\n knob: PanelSkin;\n /** The knob's width (default `height`). */\n knobWidth?: number;\n /** Over the track while focused, under the knob (a ring: a colour skin with `fillAlpha: 0`). */\n focus?: PanelSkin;\n min?: number;\n max?: number;\n /** Snap to steps of this size (0: any value). */\n step?: number;\n /** One left/right press moves this much (default `step`, or a tenth of the range). */\n navStep?: number;\n value?: number;\n onChange?: (value: number) => void;\n}\n\n/** A horizontal slider: dragged by pointer or finger anywhere on its track, stepped by left/right when focused. */\nexport class Slider extends Widget {\n onChange: ((value: number) => void) | null;\n readonly min: number;\n readonly max: number;\n private current: number;\n private readonly track: Panel;\n private readonly fill: Panel | null;\n private readonly knob: Panel;\n private readonly focusFace: Panel | null;\n private readonly onMove = (e: FederatedPointerEvent) => this.dragTo(e);\n private readonly onUp = () => this.endDrag();\n\n constructor(private readonly options: SliderOptions) {\n super();\n this.focusable = true;\n this.min = options.min ?? 0;\n this.max = options.max ?? 1;\n this.current = quantize(options.value ?? this.min, this.min, this.max, options.step);\n this.onChange = options.onChange ?? null;\n this.track = new Panel(options.track, options.width, options.height, { blocksPointer: false });\n this.fill = options.fill ? new Panel(options.fill, 0, options.height, { blocksPointer: false }) : null;\n this.knob = new Panel(options.knob, options.knobWidth ?? options.height, options.height, { blocksPointer: false });\n this.focusFace = options.focus ? new Panel(options.focus, options.width, options.height, { blocksPointer: false }) : null;\n this.addChild(this.track);\n if (this.fill) this.addChild(this.fill);\n // under the knob: drawn over it, a focus face hid the one thing the player is moving\n if (this.focusFace) this.addChild(this.focusFace);\n this.addChild(this.knob);\n this.makeInteractive();\n this.cursor = 'pointer';\n this.hitArea = new Rectangle(0, 0, 0, 0);\n this.on('pointerdown', (e) => {\n if (!this.enabled) return;\n // global moves only while dragging: a listener left on would hear every move of the session\n this.on('globalpointermove', this.onMove);\n this.on('pointerup', this.onUp);\n this.on('pointerupoutside', this.onUp);\n this.dragTo(e);\n });\n this.resize(options.width, options.height);\n }\n\n get value(): number {\n return this.current;\n }\n\n /** Sets it; `silent` skips `onChange`. */\n setValue(value: number, silent = false): this {\n const v = quantize(value, this.min, this.max, this.options.step);\n if (v === this.current) return this;\n this.current = v;\n this.redraw();\n if (!silent) this.onChange?.(v);\n return this;\n }\n\n override navigate(dir: NavDirection): boolean {\n if (dir !== 'left' && dir !== 'right') return false;\n const step = this.options.navStep ?? (this.options.step || (this.max - this.min) / 10);\n this.setValue(this.current + (dir === 'right' ? step : -step));\n return true;\n }\n\n override destroy(): void {\n this.endDrag();\n super.destroy({ children: true });\n }\n\n private dragTo(e: FederatedPointerEvent): void {\n const local = this.toLocal(e.global);\n const knobW = this.knob.boxWidth;\n // the knob's centre follows the pointer, kept on the track\n this.setValue(sliderValueAt(local.x, knobW / 2, this.boxW - knobW, this.min, this.max, this.options.step));\n }\n\n private endDrag(): void {\n this.off('globalpointermove', this.onMove);\n this.off('pointerup', this.onUp);\n this.off('pointerupoutside', this.onUp);\n }\n\n protected redraw(): void {\n const w = this.boxW;\n const h = this.boxH;\n this.track.resize(w, h);\n this.focusFace?.resize(w, h);\n if (this.focusFace) this.focusFace.visible = this.focused && this.enabled;\n if (this.hitArea instanceof Rectangle) {\n this.hitArea.width = w;\n this.hitArea.height = h;\n }\n const knobW = this.knob.boxWidth;\n const x = Math.round(sliderFraction(this.current, this.min, this.max) * (w - knobW));\n this.knob.place(x, 0);\n this.fill?.resize(x + Math.round(knobW / 2), h);\n this.alpha = this.enabled ? 1 : 0.5;\n }\n}\n"],"names":["Slider","Widget","options","quantize","Panel","Rectangle","e","value","silent","v","dir","step","local","knobW","sliderValueAt","w","h","x","sliderFraction"],"mappings":"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"}
|
package/dist/ui/Stack.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Stack.js","sources":["../../src/ui/Stack.ts"],"sourcesContent":["import type { Container } from 'pixi.js';\nimport { stackLayout, type StackOptions } from './layout/stack';\nimport { sizeOf, Widget } from './Widget';\n\n/**\n * Lays its children one after another, down or across, with a gap and padding, and takes the size they need. Call\n * `layout()` after changing a child's size; adding or removing through the stack lays out for you.\n */\nexport class Stack extends Widget {\n constructor(private readonly options: StackOptions = {}) {\n super();\n }\n\n add(...children: Container[]): this {\n for (const c of children) this.addChild(c);\n this.layout();\n this.host?.invalidateFocus();\n return this;\n }\n\n remove(child: Container, destroy = false): this {\n if (child.parent === this) this.removeChild(child);\n if (destroy) child.destroy({ children: true });\n this.layout();\n this.host?.invalidateFocus();\n return this;\n }\n\n layout(): this {\n const kids = this.children.filter((c) => c.visible);\n const snap = this.options.snap ?? this.host?.pixelPerfect ?? false;\n const result = stackLayout(kids.map(sizeOf), { ...this.options, snap });\n kids.forEach((c, i) => {\n const r = result.items[i]!;\n c.position.set(r.x, r.y);\n // stretched children take the cross size\n if (this.options.align === 'stretch' && c instanceof Widget && (c.boxWidth !== r.width || c.boxHeight !== r.height)) c.resize(r.width, r.height);\n });\n this.boxW = result.width;\n this.boxH = result.height;\n return this;\n }\n\n protected redraw(): void {}\n}\n"],"names":["Stack","Widget","options","children","c","child","destroy","kids","snap","result","stackLayout","sizeOf","i","r"],"mappings":";;AAQO,MAAMA,UAAcC,EAAO;AAAA,EAChC,YAA6BC,IAAwB,IAAI;AACvD,UAAA,GAD2B,KAAA,UAAAA;AAAA,EAE7B;AAAA,EAF6B;AAAA,EAI7B,OAAOC,GAA6B;AAClC,eAAWC,KAAKD,EAAU,MAAK,SAASC,CAAC;AACzC,gBAAK,OAAA,GACL,KAAK,MAAM,gBAAA,GACJ;AAAA,EACT;AAAA,EAEA,OAAOC,GAAkBC,IAAU,IAAa;AAC9C,WAAID,EAAM,WAAW,QAAM,KAAK,YAAYA,CAAK,GAC7CC,KAASD,EAAM,QAAQ,EAAE,UAAU,IAAM,GAC7C,KAAK,OAAA,GACL,KAAK,MAAM,gBAAA,GACJ;AAAA,EACT;AAAA,EAEA,SAAe;AACb,UAAME,IAAO,KAAK,SAAS,OAAO,CAACH,MAAMA,EAAE,OAAO,GAC5CI,IAAO,KAAK,QAAQ,QAAQ,KAAK,MAAM,gBAAgB,IACvDC,IAASC,EAAYH,EAAK,IAAII,CAAM,GAAG,EAAE,GAAG,KAAK,SAAS,MAAAH,GAAM;AACtE,WAAAD,EAAK,QAAQ,CAACH,GAAGQ,MAAM;AACrB,YAAMC,IAAIJ,EAAO,MAAMG,CAAC;AACxB,MAAAR,EAAE,SAAS,IAAIS,EAAE,GAAGA,EAAE,CAAC,GAEnB,KAAK,QAAQ,UAAU,aAAaT,aAAaH,MAAWG,EAAE,aAAaS,EAAE,SAAST,EAAE,cAAcS,EAAE,WAAST,EAAE,OAAOS,EAAE,OAAOA,EAAE,MAAM;AAAA,IACjJ,CAAC,GACD,KAAK,OAAOJ,EAAO,OACnB,KAAK,OAAOA,EAAO,QACZ;AAAA,EACT;AAAA,EAEU,SAAe;AAAA,EAAC;AAC5B;"}
|
package/dist/ui/Toggle.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Toggle.js","sources":["../../src/ui/Toggle.ts"],"sourcesContent":["import { Rectangle, Sprite, Texture } from 'pixi.js';\nimport { Panel, type PanelSkin } from './Panel';\nimport { Widget } from './Widget';\n\nexport interface ToggleOptions {\n /** The box's side (px). */\n size: number;\n /** The box. */\n box: PanelSkin;\n /** The mark drawn inside when on: a texture (a pixel check), or a filled square of this colour. */\n mark: Texture | number;\n /** Drawn over the box while focused. */\n focus?: PanelSkin;\n value?: boolean;\n onChange?: (value: boolean) => void;\n}\n\n/** A checkbox: a press, Accept, or left/right on a gamepad flips it. */\nexport class Toggle extends Widget {\n onChange: ((value: boolean) => void) | null;\n private checked: boolean;\n private readonly box: Panel;\n private readonly mark: Sprite;\n private readonly focusFace: Panel | null;\n\n constructor(private readonly options: ToggleOptions) {\n super();\n this.focusable = true;\n this.checked = options.value ?? false;\n this.onChange = options.onChange ?? null;\n this.box = new Panel(options.box, options.size, options.size, { blocksPointer: false });\n this.mark = typeof options.mark === 'number' ? new Sprite(Texture.WHITE) : new Sprite(options.mark);\n if (typeof options.mark === 'number') this.mark.tint = options.mark;\n this.focusFace = options.focus ? new Panel(options.focus, options.size, options.size, { blocksPointer: false }) : null;\n this.addChild(this.box, this.mark);\n if (this.focusFace) this.addChild(this.focusFace);\n this.makeInteractive();\n this.cursor = 'pointer';\n this.hitArea = new Rectangle(0, 0, options.size, options.size);\n this.on('pointertap', () => this.set(!this.checked));\n this.resize(options.size, options.size);\n }\n\n get value(): boolean {\n return this.checked;\n }\n\n /** Sets it; `silent` skips `onChange` (mirroring a stored setting into the widget). */\n set(value: boolean, silent = false): this {\n if (value === this.checked || (!silent && !this.enabled)) return this;\n this.checked = value;\n this.redraw();\n if (!silent) this.onChange?.(value);\n return this;\n }\n\n override activate(): void {\n this.set(!this.checked);\n }\n\n protected redraw(): void {\n const w = this.boxW;\n this.box.resize(w, this.boxH);\n this.focusFace?.resize(w, this.boxH);\n if (this.focusFace) this.focusFace.visible = this.focused && this.enabled;\n this.mark.visible = this.checked;\n if (typeof this.options.mark === 'number') {\n // a filled square a quarter in from each side\n const inset = Math.round(w / 4);\n this.mark.setSize(w - 2 * inset, this.boxH - 2 * inset);\n this.mark.position.set(inset, inset);\n } else {\n this.mark.position.set(Math.round((w - this.mark.texture.width) / 2), Math.round((this.boxH - this.mark.texture.height) / 2));\n }\n this.alpha = this.enabled ? 1 : 0.5;\n }\n}\n"],"names":["Toggle","Widget","options","Panel","Sprite","Texture","Rectangle","value","silent","w","inset"],"mappings":";;;AAkBO,MAAMA,UAAeC,EAAO;AAAA,EAOjC,YAA6BC,GAAwB;AACnD,UAAA,GAD2B,KAAA,UAAAA,GAE3B,KAAK,YAAY,IACjB,KAAK,UAAUA,EAAQ,SAAS,IAChC,KAAK,WAAWA,EAAQ,YAAY,MACpC,KAAK,MAAM,IAAIC,EAAMD,EAAQ,KAAKA,EAAQ,MAAMA,EAAQ,MAAM,EAAE,eAAe,GAAA,CAAO,GACtF,KAAK,OAAO,OAAOA,EAAQ,QAAS,WAAW,IAAIE,EAAOC,EAAQ,KAAK,IAAI,IAAID,EAAOF,EAAQ,IAAI,GAC9F,OAAOA,EAAQ,QAAS,aAAU,KAAK,KAAK,OAAOA,EAAQ,OAC/D,KAAK,YAAYA,EAAQ,QAAQ,IAAIC,EAAMD,EAAQ,OAAOA,EAAQ,MAAMA,EAAQ,MAAM,EAAE,eAAe,GAAA,CAAO,IAAI,MAClH,KAAK,SAAS,KAAK,KAAK,KAAK,IAAI,GAC7B,KAAK,aAAW,KAAK,SAAS,KAAK,SAAS,GAChD,KAAK,gBAAA,GACL,KAAK,SAAS,WACd,KAAK,UAAU,IAAII,EAAU,GAAG,GAAGJ,EAAQ,MAAMA,EAAQ,IAAI,GAC7D,KAAK,GAAG,cAAc,MAAM,KAAK,IAAI,CAAC,KAAK,OAAO,CAAC,GACnD,KAAK,OAAOA,EAAQ,MAAMA,EAAQ,IAAI;AAAA,EACxC;AAAA,EAhB6B;AAAA,EAN7B;AAAA,EACQ;AAAA,EACS;AAAA,EACA;AAAA,EACA;AAAA,EAoBjB,IAAI,QAAiB;AACnB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAIK,GAAgBC,IAAS,IAAa;AACxC,WAAID,MAAU,KAAK,WAAY,CAACC,KAAU,CAAC,KAAK,UAAiB,QACjE,KAAK,UAAUD,GACf,KAAK,OAAA,GACAC,KAAQ,KAAK,WAAWD,CAAK,GAC3B;AAAA,EACT;AAAA,EAES,WAAiB;AACxB,SAAK,IAAI,CAAC,KAAK,OAAO;AAAA,EACxB;AAAA,EAEU,SAAe;AACvB,UAAME,IAAI,KAAK;AAKf,QAJA,KAAK,IAAI,OAAOA,GAAG,KAAK,IAAI,GAC5B,KAAK,WAAW,OAAOA,GAAG,KAAK,IAAI,GAC/B,KAAK,cAAW,KAAK,UAAU,UAAU,KAAK,WAAW,KAAK,UAClE,KAAK,KAAK,UAAU,KAAK,SACrB,OAAO,KAAK,QAAQ,QAAS,UAAU;AAEzC,YAAMC,IAAQ,KAAK,MAAMD,IAAI,CAAC;AAC9B,WAAK,KAAK,QAAQA,IAAI,IAAIC,GAAO,KAAK,OAAO,IAAIA,CAAK,GACtD,KAAK,KAAK,SAAS,IAAIA,GAAOA,CAAK;AAAA,IACrC;AACE,WAAK,KAAK,SAAS,IAAI,KAAK,OAAOD,IAAI,KAAK,KAAK,QAAQ,SAAS,CAAC,GAAG,KAAK,OAAO,KAAK,OAAO,KAAK,KAAK,QAAQ,UAAU,CAAC,CAAC;AAE9H,SAAK,QAAQ,KAAK,UAAU,IAAI;AAAA,EAClC;AACF;"}
|
package/dist/ui/UiHost.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"UiHost.js","sources":["../../src/ui/UiHost.ts"],"sourcesContent":["import type { Container } from 'pixi.js';\n\n/** Something that moves on its own for a while (a draining bar, a fling); false from `tick` once it has settled. */\nexport interface UiTicking {\n tick(dtMs: number): boolean;\n}\n\n/** What a widget needs from the root it sits under; the root implements it (kept apart so the two never import each other). */\nexport interface UiHost {\n readonly isUiHost: true;\n /** Lay out and draw on whole pixels. */\n readonly pixelPerfect: boolean;\n /** A widget became focusable or stopped being (added, removed, enabled, hidden): the focus list is stale. */\n invalidateFocus(): void;\n /** Ticks `t` every frame until it reports it has settled: only what moves costs a frame. */\n animate(t: UiTicking): void;\n}\n\n/** The root a display object sits under, if any: walked up the parents, only when laying out (never per frame). */\nexport function uiHostOf(obj: Container | null): UiHost | null {\n for (let c: Container | null = obj; c; c = c.parent) {\n if ((c as unknown as Partial<UiHost>).isUiHost === true) return c as unknown as UiHost;\n }\n return null;\n}\n"],"names":["uiHostOf","obj","c"],"mappings":"AAmBO,SAASA,EAASC,GAAsC;AAC7D,WAASC,IAAsBD,GAAKC,GAAGA,IAAIA,EAAE;AAC3C,QAAKA,EAAiC,aAAa,GAAM,QAAOA;AAElE,SAAO;AACT;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"UiPointerGate.js","sources":["../../src/ui/UiPointerGate.ts"],"sourcesContent":["import { Pointer } from '../core/Pointer';\n\n/** The native events a UI widget took: a WeakSet, so an event is forgotten with the event itself. */\nconst consumed = new WeakSet<object>();\nlet installed = 0;\nlet previous: ((e: PointerEvent) => boolean) | null = null;\nlet gate: ((e: PointerEvent) => boolean) | null = null;\n\n/**\n * Keeps a press on a canvas widget from also counting as a press in the world.\n *\n * Pixi's event system listens on the canvas in the capture phase, so it hit-tests the widgets and runs their\n * `pointerdown` handlers before `Pointer`'s own listener on the same canvas sees the event. A widget marks the native\n * event (`consume`), and the gate, chained into `Pointer.ignorePress`, tells `Pointer` to leave that press out. A\n * game's own `ignorePress` keeps working: the gate asks it too.\n */\nexport const UiPointerGate = {\n /** Marks a native event (a federated event's `nativeEvent`) as taken by the UI. */\n consume(e: object | null | undefined): void {\n if (e) consumed.add(e);\n },\n\n /** Whether the UI took this native event (a game's own canvas listener may ask too). */\n wasConsumed(e: object | null | undefined): boolean {\n return !!e && consumed.has(e);\n },\n\n /** Chains the gate into `Pointer.ignorePress`; counted, so two UI roots install it once. Returns the uninstall. */\n install(): () => void {\n if (installed++ === 0) {\n previous = Pointer.ignorePress;\n gate = (e) => consumed.has(e) || (previous?.(e) ?? false);\n Pointer.ignorePress = gate;\n }\n let done = false;\n return () => {\n if (done) return;\n done = true;\n if (--installed > 0) return;\n // give back what was there, unless the game replaced the hook after us (then it owns it)\n if (Pointer.ignorePress === gate) Pointer.ignorePress = previous;\n previous = null;\n gate = null;\n };\n },\n};\n"],"names":["consumed","installed","previous","gate","UiPointerGate","Pointer","e","done"],"mappings":";AAGA,MAAMA,wBAAe,QAAA;AACrB,IAAIC,IAAY,GACZC,IAAkD,MAClDC,IAA8C;AAU3C,MAAMC,IAAgB;AAAA;AAAA,EAE3B,QAAQ,GAAoC;AAC1C,IAAI,KAAGJ,EAAS,IAAI,CAAC;AAAA,EACvB;AAAA;AAAA,EAGA,YAAY,GAAuC;AACjD,WAAO,CAAC,CAAC,KAAKA,EAAS,IAAI,CAAC;AAAA,EAC9B;AAAA;AAAA,EAGA,UAAsB;AACpB,IAAIC,QAAgB,MAClBC,IAAWG,EAAQ,aACnBF,IAAO,CAACG,MAAMN,EAAS,IAAIM,CAAC,MAAMJ,IAAWI,CAAC,KAAK,KACnDD,EAAQ,cAAcF;AAExB,QAAII,IAAO;AACX,WAAO,MAAM;AACX,MAAIA,MACJA,IAAO,IACH,IAAEN,IAAY,OAEdI,EAAQ,gBAAgBF,MAAME,EAAQ,cAAcH,IACxDA,IAAW,MACXC,IAAO;AAAA,IACT;AAAA,EACF;AACF;"}
|
package/dist/ui/UiRoot.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"UiRoot.js","sources":["../../src/ui/UiRoot.ts"],"sourcesContent":["import { Container, type Ticker } from 'pixi.js';\nimport { Stage } from '../core/Stage';\nimport { DEFAULT_NAV, FocusManager, type NavBindings, type NavInput } from './FocusManager';\nimport { anchorPosition, type ScreenAnchor } from './layout/anchor';\nimport type { Insets, Size } from './layout/geometry';\nimport type { UiHost, UiTicking } from './UiHost';\nimport { UiPointerGate } from './UiPointerGate';\nimport { sizeOf } from './Widget';\n\n/** A window that takes the screen while it is open (a `Dialog`): laid out on resize, asked on Cancel. */\nexport interface UiModal extends Container {\n /** Lays itself out for the screen's size. */\n layoutFor(viewport: Size): void;\n /** Cancel pressed inside it; true when it used it (it closed). */\n cancel(): boolean;\n}\n\nexport interface UiRootOptions {\n /** Lay out on whole pixels (default: the camera's `roundPixels`, which `pixelPerfect: true` turns on). */\n pixelPerfect?: boolean;\n /** The action names navigation reads (default `ui_up`, `ui_down`, … `ui_accept`, `ui_cancel`). */\n bindings?: Partial<NavBindings>;\n /**\n * The container the root is added to (default the stage's screen layer). Give a scene's `hud` and the UI lives and\n * dies with the scene: the scene destroys it on exit, its ticker listener and resize hook with it. On the screen\n * layer it outlives the scene that made it until `destroy` is called.\n */\n parent?: Container;\n}\n\ninterface Pin {\n child: Container;\n anchor: ScreenAnchor;\n margin: number | Partial<Insets>;\n}\n\n/**\n * The canvas UI's root, on the stage's screen layer above the world: it pins HUD pieces to screen corners and keeps\n * them there on resize, owns keyboard/gamepad focus, ticks only the widgets that are moving, opens modals with a\n * focus trap, and keeps presses on widgets from reaching the world (`UiPointerGate`).\n *\n * ```ts\n * const ui = new UiRoot(engine.stage, { parent: scene.hud }); // goes when the scene does\n * ui.pin(healthBar, 'top-left', 8);\n * // in a fixed step, between actions.poll() and endStep():\n * if (!ui.handleInput(actions) && !ui.capturesInput) movePlayer();\n * ```\n */\nexport class UiRoot extends Container implements UiHost {\n readonly isUiHost = true;\n readonly pixelPerfect: boolean;\n readonly focus: FocusManager;\n private readonly pins: Pin[] = [];\n private readonly moving = new Set<UiTicking>();\n private readonly modals: UiModal[] = [];\n private readonly cleanup: (() => void)[] = [];\n private readonly ticker: Ticker;\n private readonly onTick = (t: Ticker) => this.guardedUpdate(t.deltaMS);\n\n constructor(\n private readonly stage: Stage,\n options: UiRootOptions = {},\n ) {\n super();\n this.pixelPerfect = options.pixelPerfect ?? stage.cameras.main.roundPixels;\n this.focus = new FocusManager(this, { ...DEFAULT_NAV, ...options.bindings });\n (options.parent ?? stage.screen).addChild(this);\n this.cleanup.push(stage.onResize(() => this.layout()), UiPointerGate.install());\n // after the engine's own frame (same priority, added later): widgets tick once the game has set their values\n this.ticker = stage.app.ticker;\n this.ticker.add(this.onTick);\n }\n\n /** The screen's size in screen pixels. */\n get viewport(): Size {\n return { width: this.stage.width, height: this.stage.height };\n }\n\n /** A modal is open or a widget has focus: the game should leave its own actions alone this step. */\n get capturesInput(): boolean {\n return this.modals.length > 0 || this.focus.focused !== null;\n }\n\n /** Adds `child` and keeps it pinned to a screen anchor, `margin` in from the edges, through every resize. */\n pin(child: Container, anchor: ScreenAnchor, margin: number | Partial<Insets> = 0): this {\n if (child.parent !== this) this.addChild(child);\n const i = this.pins.findIndex((p) => p.child === child);\n if (i >= 0) this.pins.splice(i, 1);\n this.pins.push({ child, anchor, margin });\n this.place(this.pins[this.pins.length - 1]!);\n this.invalidateFocus();\n return this;\n }\n\n unpin(child: Container): this {\n const i = this.pins.findIndex((p) => p.child === child);\n if (i >= 0) this.pins.splice(i, 1);\n return this;\n }\n\n /** Places every pinned piece and open modal again (after one changed size; resizes do it by themselves). */\n layout(): void {\n for (let i = this.pins.length - 1; i >= 0; i--) {\n const p = this.pins[i]!;\n if (p.child.destroyed) this.pins.splice(i, 1);\n else this.place(p);\n }\n const view = this.viewport;\n for (const m of this.modals) m.layoutFor(view);\n }\n\n /** Opens a modal over everything: focus is trapped inside it until it closes. */\n openModal(modal: UiModal): void {\n if (this.modals.includes(modal)) return;\n this.modals.push(modal);\n this.addChild(modal);\n modal.layoutFor(this.viewport);\n this.focus.pushScope(modal, () => modal.cancel());\n }\n\n closeModal(modal: UiModal): void {\n const i = this.modals.indexOf(modal);\n if (i < 0) return;\n this.modals.splice(i, 1);\n this.focus.popScope(modal);\n if (modal.parent === this) this.removeChild(modal);\n }\n\n /** Reads one step of navigation input (call between `actions.poll()` and `endStep()`); true when the UI used it. */\n handleInput(input: NavInput): boolean {\n return this.focus.handle(input);\n }\n\n invalidateFocus(): void {\n this.focus.invalidate();\n }\n\n animate(t: UiTicking): void {\n this.moving.add(t);\n }\n\n /** Ticks what is moving; called by the ticker, or by a game driving the UI itself. */\n update(dtMs: number): void {\n // deleting the entry being visited is safe in a Set walk, so a settled widget leaves without a copy\n for (const t of this.moving) if (!t.tick(dtMs)) this.moving.delete(t);\n }\n\n override destroy(): void {\n // destroyed by its parent (a scene's hud going) and again by the game's own cleanup: the second is a no-op\n if (this.destroyed) return;\n this.ticker.remove(this.onTick);\n for (const fn of this.cleanup.splice(0)) fn();\n this.moving.clear();\n this.pins.length = 0;\n this.modals.length = 0;\n super.destroy({ children: true });\n }\n\n private place(p: Pin): void {\n const pos = anchorPosition(p.anchor, sizeOf(p.child), this.viewport, p.margin, this.pixelPerfect);\n p.child.position.set(pos.x, pos.y);\n }\n\n /** A ticker listener that throws never asks for the next frame: the UI's faults are caught and reported once. */\n private guardedUpdate(dtMs: number): void {\n try {\n this.update(dtMs);\n } catch (err) {\n this.moving.clear();\n console.error('[ui] update failed', err);\n Stage.onCaught?.('[ui] update', err);\n }\n }\n}\n"],"names":["UiRoot","Container","stage","options","FocusManager","DEFAULT_NAV","UiPointerGate","t","child","anchor","margin","i","p","view","m","modal","input","dtMs","fn","pos","anchorPosition","sizeOf","err","Stage"],"mappings":"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"}
|
package/dist/ui/Widget.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Widget.js","sources":["../../src/ui/Widget.ts"],"sourcesContent":["import { Container, type FederatedPointerEvent } from 'pixi.js';\nimport type { NavDirection } from './layout/focusNav';\nimport { snapPx, type Size } from './layout/geometry';\nimport { uiHostOf, type UiHost } from './UiHost';\nimport { UiPointerGate } from './UiPointerGate';\n\n/** Every press on a widget is the UI's, never the world's under it (see `UiPointerGate`). */\nfunction consumePress(e: FederatedPointerEvent): void {\n UiPointerGate.consume(e.nativeEvent);\n}\n\n/**\n * The base of every canvas widget: a Pixi container with a layout box (`boxWidth` × `boxHeight`, its own units, not\n * its drawn bounds), an enabled and a focused state, and the hooks keyboard and gamepad navigation call.\n *\n * A widget is a Pixi `Container`, so Container's own members are taken names: in particular `label` (Pixi's debug\n * name, a string) — a subclass field `label` holding a `Label` clashes with it; call it `caption`, `title`, … .\n */\nexport abstract class Widget extends Container {\n /** Whether the focus manager may land on it (buttons, sliders, toggles). */\n focusable = false;\n protected boxW = 0;\n protected boxH = 0;\n private isEnabled = true;\n private isFocused = false;\n\n get boxWidth(): number {\n return this.boxW;\n }\n\n get boxHeight(): number {\n return this.boxH;\n }\n\n get enabled(): boolean {\n return this.isEnabled;\n }\n\n set enabled(v: boolean) {\n if (v === this.isEnabled) return;\n this.isEnabled = v;\n this.cursor = v && this.focusable ? 'pointer' : 'default';\n this.redraw();\n this.host?.invalidateFocus();\n }\n\n get focused(): boolean {\n return this.isFocused;\n }\n\n /** Called by the focus manager. */\n setFocused(v: boolean): void {\n if (v === this.isFocused) return;\n this.isFocused = v;\n this.redraw();\n }\n\n /** Resizes the layout box (whole pixels under a pixel-perfect root) and redraws. */\n resize(width: number, height: number): this {\n const snap = this.host?.pixelPerfect ?? false;\n this.boxW = Math.max(0, snapPx(width, snap));\n this.boxH = Math.max(0, snapPx(height, snap));\n this.redraw();\n return this;\n }\n\n /** Moves it, on whole pixels under a pixel-perfect root. */\n place(x: number, y: number): this {\n const snap = this.host?.pixelPerfect ?? false;\n this.position.set(snapPx(x, snap), snapPx(y, snap));\n return this;\n }\n\n /** Focusable now: enabled, shown, and every parent shown. */\n canFocus(): boolean {\n if (!this.focusable || !this.isEnabled) return false;\n for (let c: Container | null = this; c; c = c.parent) if (!c.visible) return false;\n return true;\n }\n\n /** Accept pressed while focused (Enter, the pad's A). */\n activate(): void {}\n\n /** A direction pressed while focused; true when the widget used it (a slider stepping) so focus stays. */\n navigate(_dir: NavDirection): boolean {\n return false;\n }\n\n /** The root it sits under, looked up when needed: a widget may be built before it is added. */\n protected get host(): UiHost | null {\n return uiHostOf(this);\n }\n\n /**\n * Hit-tested by Pixi, and every press on it is kept from the world. A widget whose drawing has gaps (a scroll\n * view, an empty panel) also sets a `hitArea` over its box, so a press between its pieces is not the world's.\n */\n protected makeInteractive(): void {\n this.eventMode = 'static';\n this.on('pointerdown', consumePress);\n }\n\n /**\n * A child widget's box changed size by itself (a `Label` given a longer text). A widget that places a child by its\n * size (a `Button` centring its content) lays it out again here; the default does nothing, so a `Stack` still waits\n * for its `layout()` and a change does not ripple up through every container.\n */\n protected childResized(_child: Widget): void {}\n\n /** Tells the parent widget this one's box changed size by itself. */\n protected notifyResized(): void {\n const p = this.parent;\n if (p instanceof Widget) p.childResized(this);\n }\n\n /** Draws the current size and state. */\n protected abstract redraw(): void;\n}\n\n/** A child's layout size: a widget's box, anything else its drawn bounds. */\nexport function sizeOf(c: Container): Size {\n if (c instanceof Widget) return { width: c.boxWidth, height: c.boxHeight };\n const b = c.getLocalBounds();\n return { width: b.maxX, height: b.maxY };\n}\n"],"names":["consumePress","UiPointerGate","Widget","Container","v","width","height","snap","snapPx","x","y","c","_dir","uiHostOf","_child","p","sizeOf","b"],"mappings":";;;;AAOA,SAASA,EAAa,GAAgC;AACpD,EAAAC,EAAc,QAAQ,EAAE,WAAW;AACrC;AASO,MAAeC,UAAeC,EAAU;AAAA;AAAA,EAE7C,YAAY;AAAA,EACF,OAAO;AAAA,EACP,OAAO;AAAA,EACT,YAAY;AAAA,EACZ,YAAY;AAAA,EAEpB,IAAI,WAAmB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,YAAoB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,UAAmB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,QAAQC,GAAY;AACtB,IAAIA,MAAM,KAAK,cACf,KAAK,YAAYA,GACjB,KAAK,SAASA,KAAK,KAAK,YAAY,YAAY,WAChD,KAAK,OAAA,GACL,KAAK,MAAM,gBAAA;AAAA,EACb;AAAA,EAEA,IAAI,UAAmB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,WAAWA,GAAkB;AAC3B,IAAIA,MAAM,KAAK,cACf,KAAK,YAAYA,GACjB,KAAK,OAAA;AAAA,EACP;AAAA;AAAA,EAGA,OAAOC,GAAeC,GAAsB;AAC1C,UAAMC,IAAO,KAAK,MAAM,gBAAgB;AACxC,gBAAK,OAAO,KAAK,IAAI,GAAGC,EAAOH,GAAOE,CAAI,CAAC,GAC3C,KAAK,OAAO,KAAK,IAAI,GAAGC,EAAOF,GAAQC,CAAI,CAAC,GAC5C,KAAK,OAAA,GACE;AAAA,EACT;AAAA;AAAA,EAGA,MAAME,GAAWC,GAAiB;AAChC,UAAMH,IAAO,KAAK,MAAM,gBAAgB;AACxC,gBAAK,SAAS,IAAIC,EAAOC,GAAGF,CAAI,GAAGC,EAAOE,GAAGH,CAAI,CAAC,GAC3C;AAAA,EACT;AAAA;AAAA,EAGA,WAAoB;AAClB,QAAI,CAAC,KAAK,aAAa,CAAC,KAAK,UAAW,QAAO;AAC/C,aAASI,IAAsB,MAAMA,GAAGA,IAAIA,EAAE,OAAQ,KAAI,CAACA,EAAE,QAAS,QAAO;AAC7E,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,WAAiB;AAAA,EAAC;AAAA;AAAA,EAGlB,SAASC,GAA6B;AACpC,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,IAAc,OAAsB;AAClC,WAAOC,EAAS,IAAI;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMU,kBAAwB;AAChC,SAAK,YAAY,UACjB,KAAK,GAAG,eAAeb,CAAY;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOU,aAAac,GAAsB;AAAA,EAAC;AAAA;AAAA,EAGpC,gBAAsB;AAC9B,UAAMC,IAAI,KAAK;AACf,IAAIA,aAAab,KAAQa,EAAE,aAAa,IAAI;AAAA,EAC9C;AAIF;AAGO,SAASC,EAAOL,GAAoB;AACzC,MAAIA,aAAaT,EAAQ,QAAO,EAAE,OAAOS,EAAE,UAAU,QAAQA,EAAE,UAAA;AAC/D,QAAMM,IAAIN,EAAE,eAAA;AACZ,SAAO,EAAE,OAAOM,EAAE,MAAM,QAAQA,EAAE,KAAA;AACpC;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"DomOverlay.js","sources":["../../../src/ui/dom/DomOverlay.ts"],"sourcesContent":["export interface DomOverlayOptions {\n /** Added to the root element (for the game's CSS). */\n className?: string;\n /** Stacking order over the canvas (default 1). */\n zIndex?: number;\n /**\n * The canvas's size in screen pixels (`() => app.screen`): when CSS stretches the canvas past it (a small fixed\n * canvas scaled up), `cssScale` is how much, and world placement follows it. Default: no stretch.\n */\n logicalSize?: () => { width: number; height: number };\n}\n\n/**\n * The element a DOM (or React) UI is mounted in, laid exactly over the canvas and resized with it. It sits beside the\n * canvas in the same parent, so it shares the canvas's positioned ancestor and needs no change to the page's CSS.\n *\n * The root lets presses through (`pointer-events: none`) so the world stays playable between panels; give what the\n * player presses `pointer-events: auto` (`DomOverlay.interactive`). `content` is drawn at `scale` (a whole multiple\n * for pixel-art UI): laid out on a box `1 / scale` the overlay and scaled back up, so a window sized for 320 × 180\n * fills the screen at ×4 with sharp edges.\n */\nexport class DomOverlay {\n readonly root: HTMLDivElement;\n /** Where the UI goes: scaled by `scale`. */\n readonly content: HTMLDivElement;\n private scaleNow = 1;\n private cssW = 0;\n private cssH = 0;\n private stretch = 1;\n private readonly observer: ResizeObserver | null;\n private readonly onWindow = () => this.sync();\n\n constructor(\n readonly canvas: HTMLCanvasElement,\n private readonly options: DomOverlayOptions = {},\n ) {\n const doc = canvas.ownerDocument;\n this.root = doc.createElement('div');\n this.content = doc.createElement('div');\n if (options.className) this.root.className = options.className;\n Object.assign(this.root.style, { position: 'absolute', overflow: 'hidden', pointerEvents: 'none', zIndex: String(options.zIndex ?? 1) });\n Object.assign(this.content.style, { position: 'absolute', left: '0', top: '0', transformOrigin: '0 0', pointerEvents: 'none' });\n this.root.appendChild(this.content);\n canvas.insertAdjacentElement('afterend', this.root);\n // the canvas's box changes on its own (a resizeTo parent, CSS), not only with the window\n this.observer = typeof ResizeObserver === 'function' ? new ResizeObserver(this.onWindow) : null;\n this.observer?.observe(canvas);\n window.addEventListener('resize', this.onWindow);\n this.sync();\n }\n\n /** Lets presses on `el` reach it (the overlay's root and content let them through to the canvas). */\n static interactive(el: HTMLElement): HTMLElement {\n el.style.pointerEvents = 'auto';\n return el;\n }\n\n /** The overlay's size in CSS pixels (the canvas's box). */\n get width(): number {\n return this.cssW;\n }\n\n get height(): number {\n return this.cssH;\n }\n\n /** CSS pixels per screen pixel of the canvas: 1 unless CSS stretches it. */\n get cssScale(): number {\n return this.stretch;\n }\n\n get scale(): number {\n return this.scaleNow;\n }\n\n /** Draws `content` at `scale` (also published as the CSS variable `--ui-scale`). */\n setScale(scale: number): void {\n if (scale === this.scaleNow || !(scale > 0)) return;\n this.scaleNow = scale;\n this.applySize();\n }\n\n /** Lines the overlay up with the canvas again; resizes do it by themselves. */\n sync(): void {\n const c = this.canvas;\n const w = c.clientWidth;\n const h = c.clientHeight;\n const s = this.root.style;\n s.left = `${c.offsetLeft + c.clientLeft}px`;\n s.top = `${c.offsetTop + c.clientTop}px`;\n if (w === this.cssW && h === this.cssH) return;\n this.cssW = w;\n this.cssH = h;\n s.width = `${w}px`;\n s.height = `${h}px`;\n const logical = this.options.logicalSize?.();\n this.stretch = logical && logical.width > 0 ? w / logical.width : 1;\n this.applySize();\n }\n\n destroy(): void {\n this.observer?.disconnect();\n window.removeEventListener('resize', this.onWindow);\n this.root.remove();\n }\n\n private applySize(): void {\n const k = this.scaleNow;\n const s = this.content.style;\n s.width = `${this.cssW / k}px`;\n s.height = `${this.cssH / k}px`;\n s.transform = k === 1 ? '' : `scale(${k})`;\n this.root.style.setProperty('--ui-scale', String(k));\n }\n}\n"],"names":["DomOverlay","canvas","options","doc","el","scale","c","w","h","s","logical","k"],"mappings":"AAqBO,MAAMA,EAAW;AAAA,EAWtB,YACWC,GACQC,IAA6B,IAC9C;AAFS,SAAA,SAAAD,GACQ,KAAA,UAAAC;AAEjB,UAAMC,IAAMF,EAAO;AACnB,SAAK,OAAOE,EAAI,cAAc,KAAK,GACnC,KAAK,UAAUA,EAAI,cAAc,KAAK,GAClCD,EAAQ,cAAW,KAAK,KAAK,YAAYA,EAAQ,YACrD,OAAO,OAAO,KAAK,KAAK,OAAO,EAAE,UAAU,YAAY,UAAU,UAAU,eAAe,QAAQ,QAAQ,OAAOA,EAAQ,UAAU,CAAC,GAAG,GACvI,OAAO,OAAO,KAAK,QAAQ,OAAO,EAAE,UAAU,YAAY,MAAM,KAAK,KAAK,KAAK,iBAAiB,OAAO,eAAe,QAAQ,GAC9H,KAAK,KAAK,YAAY,KAAK,OAAO,GAClCD,EAAO,sBAAsB,YAAY,KAAK,IAAI,GAElD,KAAK,WAAW,OAAO,kBAAmB,aAAa,IAAI,eAAe,KAAK,QAAQ,IAAI,MAC3F,KAAK,UAAU,QAAQA,CAAM,GAC7B,OAAO,iBAAiB,UAAU,KAAK,QAAQ,GAC/C,KAAK,KAAA;AAAA,EACP;AAAA,EAhBW;AAAA,EACQ;AAAA,EAZV;AAAA;AAAA,EAEA;AAAA,EACD,WAAW;AAAA,EACX,OAAO;AAAA,EACP,OAAO;AAAA,EACP,UAAU;AAAA,EACD;AAAA,EACA,WAAW,MAAM,KAAK,KAAA;AAAA;AAAA,EAsBvC,OAAO,YAAYG,GAA8B;AAC/C,WAAAA,EAAG,MAAM,gBAAgB,QAClBA;AAAA,EACT;AAAA;AAAA,EAGA,IAAI,QAAgB;AAClB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,SAAiB;AACnB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,WAAmB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,QAAgB;AAClB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,SAASC,GAAqB;AAC5B,IAAIA,MAAU,KAAK,YAAY,EAAEA,IAAQ,OACzC,KAAK,WAAWA,GAChB,KAAK,UAAA;AAAA,EACP;AAAA;AAAA,EAGA,OAAa;AACX,UAAMC,IAAI,KAAK,QACTC,IAAID,EAAE,aACNE,IAAIF,EAAE,cACNG,IAAI,KAAK,KAAK;AAGpB,QAFAA,EAAE,OAAO,GAAGH,EAAE,aAAaA,EAAE,UAAU,MACvCG,EAAE,MAAM,GAAGH,EAAE,YAAYA,EAAE,SAAS,MAChCC,MAAM,KAAK,QAAQC,MAAM,KAAK,KAAM;AACxC,SAAK,OAAOD,GACZ,KAAK,OAAOC,GACZC,EAAE,QAAQ,GAAGF,CAAC,MACdE,EAAE,SAAS,GAAGD,CAAC;AACf,UAAME,IAAU,KAAK,QAAQ,cAAA;AAC7B,SAAK,UAAUA,KAAWA,EAAQ,QAAQ,IAAIH,IAAIG,EAAQ,QAAQ,GAClE,KAAK,UAAA;AAAA,EACP;AAAA,EAEA,UAAgB;AACd,SAAK,UAAU,WAAA,GACf,OAAO,oBAAoB,UAAU,KAAK,QAAQ,GAClD,KAAK,KAAK,OAAA;AAAA,EACZ;AAAA,EAEQ,YAAkB;AACxB,UAAMC,IAAI,KAAK,UACT,IAAI,KAAK,QAAQ;AACvB,MAAE,QAAQ,GAAG,KAAK,OAAOA,CAAC,MAC1B,EAAE,SAAS,GAAG,KAAK,OAAOA,CAAC,MAC3B,EAAE,YAAYA,MAAM,IAAI,KAAK,SAASA,CAAC,KACvC,KAAK,KAAK,MAAM,YAAY,cAAc,OAAOA,CAAC,CAAC;AAAA,EACrD;AACF;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"DomWorldAnchors.js","sources":["../../../src/ui/dom/DomWorldAnchors.ts"],"sourcesContent":["import { UPDATE_PRIORITY, type Ticker } from 'pixi.js';\nimport type { DomOverlay } from './DomOverlay';\nimport { onScreen, worldToScreen, type LayerTransform } from './worldToScreen';\n\nexport interface WorldAnchorOptions {\n /** Moved this far after projecting (CSS px): a name 20 px over the feet. */\n offsetX?: number;\n offsetY?: number;\n /**\n * Which point of the element sits on the spot, as fractions of its size (default 0.5, 1: centred above it).\n * Written as a CSS translate in percent, so the element is never measured.\n */\n alignX?: number;\n alignY?: number;\n /** Kept shown this far past the screen's edge (default 64 px), so a wide label does not pop out early. */\n margin?: number;\n}\n\n/** Anything with a world position read every frame: an entity's position object, a sprite. */\nexport interface WorldPoint {\n readonly x: number;\n readonly y: number;\n}\n\ninterface Anchor {\n el: HTMLElement;\n target: WorldPoint;\n ox: number;\n oy: number;\n align: string;\n margin: number;\n lastX: number;\n lastY: number;\n shown: boolean;\n}\n\nconst point = { x: 0, y: 0 };\n\n/**\n * DOM elements that follow points in the world (names over heads, speech bubbles, markers): placed every frame\n * through the world layer's own transform, on whole CSS pixels, and hidden off screen. A style is written only when\n * the rounded position or visibility changed, so a still crowd costs no DOM writes.\n */\nexport class DomWorldAnchors {\n private readonly anchors: Anchor[] = [];\n private ticker: Ticker | null = null;\n private readonly onTick = () => this.update();\n\n constructor(\n private readonly overlay: DomOverlay,\n /** The layer the points are in: `engine.stage.world`. */\n private readonly layer: LayerTransform,\n ) {}\n\n /** Places `el` (put inside the overlay's content if it is not already there) on `target`. Returns the remover. */\n add(el: HTMLElement, target: WorldPoint, options: WorldAnchorOptions = {}): () => void {\n if (!el.parentElement) this.overlay.content.appendChild(el);\n el.style.position = 'absolute';\n el.style.left = '0';\n el.style.top = '0';\n const ax = (options.alignX ?? 0.5) * 100;\n const ay = (options.alignY ?? 1) * 100;\n const a: Anchor = {\n el,\n target,\n ox: options.offsetX ?? 0,\n oy: options.offsetY ?? 0,\n align: ax === 0 && ay === 0 ? '' : ` translate(${-ax}%, ${-ay}%)`,\n margin: options.margin ?? 64,\n lastX: NaN,\n lastY: NaN,\n shown: true,\n };\n this.anchors.push(a);\n return () => this.remove(el);\n }\n\n remove(el: HTMLElement): void {\n const i = this.anchors.findIndex((a) => a.el === el);\n if (i >= 0) this.anchors.splice(i, 1);\n }\n\n /**\n * Runs `update` on `ticker` after Pixi renders (`UTILITY` priority), so it reads the camera as this frame drew it,\n * shake included. The returned function stops it.\n */\n attach(ticker: Ticker): () => void {\n this.detach();\n this.ticker = ticker;\n ticker.add(this.onTick, undefined, UPDATE_PRIORITY.UTILITY);\n return () => this.detach();\n }\n\n detach(): void {\n this.ticker?.remove(this.onTick);\n this.ticker = null;\n }\n\n /** Places every element for this frame. */\n update(): void {\n // the overlay's content is scaled: positions are written in its own units\n const k = this.overlay.cssScale / this.overlay.scale;\n const w = this.overlay.width / this.overlay.scale;\n const h = this.overlay.height / this.overlay.scale;\n for (const a of this.anchors) {\n worldToScreen(this.layer, a.target.x, a.target.y, point, k);\n const x = Math.round(point.x + a.ox);\n const y = Math.round(point.y + a.oy);\n const show = onScreen(x, y, w, h, a.margin);\n if (show !== a.shown) {\n a.shown = show;\n a.el.style.display = show ? '' : 'none';\n }\n if (!show || (x === a.lastX && y === a.lastY)) continue;\n a.lastX = x;\n a.lastY = y;\n a.el.style.transform = `translate3d(${x}px, ${y}px, 0)${a.align}`;\n }\n }\n\n destroy(): void {\n this.detach();\n this.anchors.length = 0;\n }\n}\n"],"names":["point","DomWorldAnchors","overlay","layer","el","target","options","ax","ay","a","i","ticker","UPDATE_PRIORITY","k","w","h","worldToScreen","x","y","show","onScreen"],"mappings":";;AAoCA,MAAMA,IAAQ,EAAE,GAAG,GAAG,GAAG,EAAA;AAOlB,MAAMC,EAAgB;AAAA,EAK3B,YACmBC,GAEAC,GACjB;AAHiB,SAAA,UAAAD,GAEA,KAAA,QAAAC;AAAA,EAChB;AAAA,EAHgB;AAAA,EAEA;AAAA,EAPF,UAAoB,CAAA;AAAA,EAC7B,SAAwB;AAAA,EACf,SAAS,MAAM,KAAK,OAAA;AAAA;AAAA,EASrC,IAAIC,GAAiBC,GAAoBC,IAA8B,CAAA,GAAgB;AACrF,IAAKF,EAAG,sBAAoB,QAAQ,QAAQ,YAAYA,CAAE,GAC1DA,EAAG,MAAM,WAAW,YACpBA,EAAG,MAAM,OAAO,KAChBA,EAAG,MAAM,MAAM;AACf,UAAMG,KAAMD,EAAQ,UAAU,OAAO,KAC/BE,KAAMF,EAAQ,UAAU,KAAK,KAC7BG,IAAY;AAAA,MAChB,IAAAL;AAAA,MACA,QAAAC;AAAA,MACA,IAAIC,EAAQ,WAAW;AAAA,MACvB,IAAIA,EAAQ,WAAW;AAAA,MACvB,OAAOC,MAAO,KAAKC,MAAO,IAAI,KAAK,cAAc,CAACD,CAAE,MAAM,CAACC,CAAE;AAAA,MAC7D,QAAQF,EAAQ,UAAU;AAAA,MAC1B,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO;AAAA,IAAA;AAET,gBAAK,QAAQ,KAAKG,CAAC,GACZ,MAAM,KAAK,OAAOL,CAAE;AAAA,EAC7B;AAAA,EAEA,OAAOA,GAAuB;AAC5B,UAAMM,IAAI,KAAK,QAAQ,UAAU,CAAC,MAAM,EAAE,OAAON,CAAE;AACnD,IAAIM,KAAK,KAAG,KAAK,QAAQ,OAAOA,GAAG,CAAC;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAOC,GAA4B;AACjC,gBAAK,OAAA,GACL,KAAK,SAASA,GACdA,EAAO,IAAI,KAAK,QAAQ,QAAWC,EAAgB,OAAO,GACnD,MAAM,KAAK,OAAA;AAAA,EACpB;AAAA,EAEA,SAAe;AACb,SAAK,QAAQ,OAAO,KAAK,MAAM,GAC/B,KAAK,SAAS;AAAA,EAChB;AAAA;AAAA,EAGA,SAAe;AAEb,UAAMC,IAAI,KAAK,QAAQ,WAAW,KAAK,QAAQ,OACzCC,IAAI,KAAK,QAAQ,QAAQ,KAAK,QAAQ,OACtCC,IAAI,KAAK,QAAQ,SAAS,KAAK,QAAQ;AAC7C,eAAWN,KAAK,KAAK,SAAS;AAC5B,MAAAO,EAAc,KAAK,OAAOP,EAAE,OAAO,GAAGA,EAAE,OAAO,GAAGT,GAAOa,CAAC;AAC1D,YAAMI,IAAI,KAAK,MAAMjB,EAAM,IAAIS,EAAE,EAAE,GAC7BS,IAAI,KAAK,MAAMlB,EAAM,IAAIS,EAAE,EAAE,GAC7BU,IAAOC,EAASH,GAAGC,GAAGJ,GAAGC,GAAGN,EAAE,MAAM;AAK1C,MAJIU,MAASV,EAAE,UACbA,EAAE,QAAQU,GACVV,EAAE,GAAG,MAAM,UAAUU,IAAO,KAAK,SAE/B,GAACA,KAASF,MAAMR,EAAE,SAASS,MAAMT,EAAE,WACvCA,EAAE,QAAQQ,GACVR,EAAE,QAAQS,GACVT,EAAE,GAAG,MAAM,YAAY,eAAeQ,CAAC,OAAOC,CAAC,SAAST,EAAE,KAAK;AAAA,IACjE;AAAA,EACF;AAAA,EAEA,UAAgB;AACd,SAAK,OAAA,GACL,KAAK,QAAQ,SAAS;AAAA,EACxB;AACF;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"InputShield.js","sources":["../../../src/ui/dom/InputShield.ts"],"sourcesContent":["import { isEditable, isInside, shouldShield, type ShieldOptions } from './shieldDecision';\n\n/** The events checked on their way up out of the overlay. */\nconst EVENTS = ['keydown', 'keypress', 'pointerdown', 'mousedown', 'touchstart', 'click', 'dblclick', 'wheel', 'contextmenu'] as const;\n\n/**\n * Keeps input meant for the DOM UI from reaching the engine. It listens on the overlay's root as events bubble out\n * of it and stops the ones `shouldShield` names, so the element under them still gets them (a text field still\n * types) but listeners on the window and document (`Keyboard`, a game's own) never hear them.\n *\n * Presses on the overlay never reach `Pointer` anyway, which counts presses only on the canvas; the shield covers\n * keys, wheel and window-level listeners. Releases always pass, or a key held when a field took focus would stick.\n */\nexport class InputShield {\n private readonly handler = (e: Event) => this.check(e);\n\n constructor(\n private readonly root: HTMLElement,\n private readonly options: ShieldOptions = {},\n ) {\n for (const type of EVENTS) root.addEventListener(type, this.handler, { passive: false });\n }\n\n /** Whether the player is typing in a field of the UI now (a game may also skip its hotkeys). */\n get typing(): boolean {\n const active = this.root.ownerDocument.activeElement;\n return isEditable(active as HTMLElement | null) && isInside(active as HTMLElement | null, this.root);\n }\n\n destroy(): void {\n for (const type of EVENTS) this.root.removeEventListener(type, this.handler);\n }\n\n private check(e: Event): void {\n const target = e.target as HTMLElement | null;\n const code = (e as KeyboardEvent).code;\n const facts = { insideUi: isInside(target, this.root), editable: isEditable(target), ...(code === undefined ? {} : { code }) };\n if (shouldShield(e.type, facts, this.options)) e.stopPropagation();\n }\n}\n"],"names":["EVENTS","InputShield","root","options","type","e","active","isEditable","isInside","target","code","facts","shouldShield"],"mappings":";AAGA,MAAMA,IAAS,CAAC,WAAW,YAAY,eAAe,aAAa,cAAc,SAAS,YAAY,SAAS,aAAa;AAUrH,MAAMC,EAAY;AAAA,EAGvB,YACmBC,GACAC,IAAyB,IAC1C;AAFiB,SAAA,OAAAD,GACA,KAAA,UAAAC;AAEjB,eAAWC,KAAQJ,EAAQ,CAAAE,EAAK,iBAAiBE,GAAM,KAAK,SAAS,EAAE,SAAS,GAAA,CAAO;AAAA,EACzF;AAAA,EAJmB;AAAA,EACA;AAAA,EAJF,UAAU,CAACC,MAAa,KAAK,MAAMA,CAAC;AAAA;AAAA,EAUrD,IAAI,SAAkB;AACpB,UAAMC,IAAS,KAAK,KAAK,cAAc;AACvC,WAAOC,EAAWD,CAA4B,KAAKE,EAASF,GAA8B,KAAK,IAAI;AAAA,EACrG;AAAA,EAEA,UAAgB;AACd,eAAWF,KAAQJ,EAAQ,MAAK,KAAK,oBAAoBI,GAAM,KAAK,OAAO;AAAA,EAC7E;AAAA,EAEQ,MAAMC,GAAgB;AAC5B,UAAMI,IAASJ,EAAE,QACXK,IAAQL,EAAoB,MAC5BM,IAAQ,EAAE,UAAUH,EAASC,GAAQ,KAAK,IAAI,GAAG,UAAUF,EAAWE,CAAM,GAAG,GAAIC,MAAS,SAAY,CAAA,IAAK,EAAE,MAAAA,IAAK;AAC1H,IAAIE,EAAaP,EAAE,MAAMM,GAAO,KAAK,OAAO,OAAK,gBAAA;AAAA,EACnD;AACF;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"createStore.js","sources":["../../../src/ui/dom/createStore.ts"],"sourcesContent":["export type StoreListener<T> = (state: T, previous: T) => void;\n\n/**\n * A tiny shared state store between the game and its UI, framework-free. Same shape as the engine's settings store\n * and as zustand's vanilla stores (`getState`, `setState`, `subscribe`), so React reads it with\n * `useSyncExternalStore(store.subscribe, store.getState)`.\n */\nexport interface Store<T extends object> {\n getState(): T;\n /**\n * Merges a patch (or what a function of the state returns) into a new state object. A patch that changes nothing\n * notifies nobody: a game may set the player's health every frame and the UI renders only when it moved.\n */\n setState(patch: Partial<T> | ((state: T) => Partial<T>)): void;\n /** Calls `listener` after every change; the returned function unsubscribes. */\n subscribe(listener: StoreListener<T>): () => void;\n /** Calls `listener` only when `select(state)` changes (by `equals`, default `Object.is`). */\n select<S>(select: (state: T) => S, listener: (value: S, previous: S) => void, equals?: (a: S, b: S) => boolean): () => void;\n /** Back to the state it was made with. */\n reset(): void;\n}\n\nexport function createStore<T extends object>(initial: T): Store<T> {\n let state = initial;\n // replaced, never edited, on (un)subscribe: setState walks it without a copy, and a listener that unsubscribes\n // while it runs does not shift the walk\n let listeners: readonly StoreListener<T>[] = [];\n\n const store: Store<T> = {\n getState: () => state,\n setState(patch) {\n const p = typeof patch === 'function' ? patch(state) : patch;\n let changed = false;\n for (const k in p) {\n if (!Object.is(p[k], state[k])) {\n changed = true;\n break;\n }\n }\n if (!changed) return;\n const previous = state;\n state = { ...state, ...p };\n for (const l of listeners) l(state, previous);\n },\n subscribe(listener) {\n listeners = [...listeners, listener];\n return () => {\n listeners = listeners.filter((l) => l !== listener);\n };\n },\n select(select, listener, equals = Object.is) {\n let current = select(state);\n return store.subscribe((s) => {\n const next = select(s);\n if (equals(next, current)) return;\n const previous = current;\n current = next;\n listener(next, previous);\n });\n },\n reset() {\n const previous = state;\n state = initial;\n if (previous !== initial) for (const l of listeners) l(state, previous);\n },\n };\n return store;\n}\n"],"names":["createStore","initial","state","listeners","store","patch","p","changed","k","previous","l","listener","select","equals","current","s","next"],"mappings":"AAsBO,SAASA,EAA8BC,GAAsB;AAClE,MAAIC,IAAQD,GAGRE,IAAyC,CAAA;AAE7C,QAAMC,IAAkB;AAAA,IACtB,UAAU,MAAMF;AAAA,IAChB,SAASG,GAAO;AACd,YAAMC,IAAI,OAAOD,KAAU,aAAaA,EAAMH,CAAK,IAAIG;AACvD,UAAIE,IAAU;AACd,iBAAWC,KAAKF;AACd,YAAI,CAAC,OAAO,GAAGA,EAAEE,CAAC,GAAGN,EAAMM,CAAC,CAAC,GAAG;AAC9B,UAAAD,IAAU;AACV;AAAA,QACF;AAEF,UAAI,CAACA,EAAS;AACd,YAAME,IAAWP;AACjB,MAAAA,IAAQ,EAAE,GAAGA,GAAO,GAAGI,EAAA;AACvB,iBAAWI,KAAKP,EAAW,CAAAO,EAAER,GAAOO,CAAQ;AAAA,IAC9C;AAAA,IACA,UAAUE,GAAU;AAClB,aAAAR,IAAY,CAAC,GAAGA,GAAWQ,CAAQ,GAC5B,MAAM;AACX,QAAAR,IAAYA,EAAU,OAAO,CAACO,MAAMA,MAAMC,CAAQ;AAAA,MACpD;AAAA,IACF;AAAA,IACA,OAAOC,GAAQD,GAAUE,IAAS,OAAO,IAAI;AAC3C,UAAIC,IAAUF,EAAOV,CAAK;AAC1B,aAAOE,EAAM,UAAU,CAACW,MAAM;AAC5B,cAAMC,IAAOJ,EAAOG,CAAC;AACrB,YAAIF,EAAOG,GAAMF,CAAO,EAAG;AAC3B,cAAML,IAAWK;AACjB,QAAAA,IAAUE,GACVL,EAASK,GAAMP,CAAQ;AAAA,MACzB,CAAC;AAAA,IACH;AAAA,IACA,QAAQ;AACN,YAAMA,IAAWP;AAEjB,UADAA,IAAQD,GACJQ,MAAaR,EAAS,YAAWS,KAAKP,EAAW,CAAAO,EAAER,GAAOO,CAAQ;AAAA,IACxE;AAAA,EAAA;AAEF,SAAOL;AACT;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"shieldDecision.js","sources":["../../../src/ui/dom/shieldDecision.ts"],"sourcesContent":["/** The few fields of a DOM element the decision reads; real elements fit, and tests pass plain objects. */\nexport interface ElementLike {\n readonly tagName?: string;\n readonly type?: string;\n readonly isContentEditable?: boolean;\n readonly parentElement?: ElementLike | null;\n}\n\n/** `<input>` types that take typing; the rest (checkbox, range, button…) are pressed, not typed into. */\nconst TYPED_INPUTS = new Set(['text', 'search', 'email', 'password', 'number', 'url', 'tel', 'date', 'time', 'datetime-local', 'month', 'week', '']);\n\n/** Whether keys pressed on this element are typing (a text field, a text area, a select, editable content). */\nexport function isEditable(el: ElementLike | null | undefined): boolean {\n if (!el) return false;\n if (el.isContentEditable) return true;\n const tag = el.tagName?.toUpperCase();\n if (tag === 'TEXTAREA' || tag === 'SELECT') return true;\n return tag === 'INPUT' && TYPED_INPUTS.has((el.type ?? '').toLowerCase());\n}\n\n/** Whether `el` is `root` or inside it. */\nexport function isInside(el: ElementLike | null | undefined, root: ElementLike): boolean {\n for (let e = el; e; e = e.parentElement) if (e === root) return true;\n return false;\n}\n\nexport interface ShieldFacts {\n /** The event's target is in the DOM UI overlay. */\n insideUi: boolean;\n /** The target takes typing (`isEditable`). */\n editable: boolean;\n /** `KeyboardEvent.code`, for key events. */\n code?: string;\n}\n\nexport interface ShieldOptions {\n /**\n * Keys a focused, non-typing UI element (a button) keeps from the game: the ones that press it (default Enter,\n * Space). Others still reach the game, so a button left focused after a click does not stop the hero walking.\n */\n uiKeys?: readonly string[];\n}\n\nconst UI_KEYS = ['Enter', 'NumpadEnter', 'Space'];\n\n/**\n * Whether an event on the DOM UI must be kept from the engine's input. Releases (`keyup`, `pointerup`,\n * `pointercancel`) always go through, or a key or button held when the UI took over would stay down for good; moves\n * go through so aiming follows the pointer over a panel. Presses, wheel and context menus on the UI stay in the UI,\n * and so does every key typed into a field.\n */\nexport function shouldShield(type: string, facts: ShieldFacts, options: ShieldOptions = {}): boolean {\n if (!facts.insideUi) return false;\n switch (type) {\n case 'keydown':\n case 'keypress':\n if (facts.editable) return true;\n return facts.code !== undefined && (options.uiKeys ?? UI_KEYS).includes(facts.code);\n case 'pointerdown':\n case 'mousedown':\n case 'touchstart':\n case 'click':\n case 'dblclick':\n case 'wheel':\n case 'contextmenu':\n return true;\n default:\n return false;\n }\n}\n"],"names":["TYPED_INPUTS","isEditable","el","tag","isInside","root","e","UI_KEYS","shouldShield","type","facts","options"],"mappings":"AASA,MAAMA,IAAe,oBAAI,IAAI,CAAC,QAAQ,UAAU,SAAS,YAAY,UAAU,OAAO,OAAO,QAAQ,QAAQ,kBAAkB,SAAS,QAAQ,EAAE,CAAC;AAG5I,SAASC,EAAWC,GAA6C;AACtE,MAAI,CAACA,EAAI,QAAO;AAChB,MAAIA,EAAG,kBAAmB,QAAO;AACjC,QAAMC,IAAMD,EAAG,SAAS,YAAA;AACxB,SAAIC,MAAQ,cAAcA,MAAQ,WAAiB,KAC5CA,MAAQ,WAAWH,EAAa,KAAKE,EAAG,QAAQ,IAAI,aAAa;AAC1E;AAGO,SAASE,EAASF,GAAoCG,GAA4B;AACvF,WAASC,IAAIJ,GAAII,GAAGA,IAAIA,EAAE,cAAe,KAAIA,MAAMD,EAAM,QAAO;AAChE,SAAO;AACT;AAmBA,MAAME,IAAU,CAAC,SAAS,eAAe,OAAO;AAQzC,SAASC,EAAaC,GAAcC,GAAoBC,IAAyB,CAAA,GAAa;AACnG,MAAI,CAACD,EAAM,SAAU,QAAO;AAC5B,UAAQD,GAAA;AAAA,IACN,KAAK;AAAA,IACL,KAAK;AACH,aAAIC,EAAM,WAAiB,KACpBA,EAAM,SAAS,WAAcC,EAAQ,UAAUJ,GAAS,SAASG,EAAM,IAAI;AAAA,IACpF,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EAAA;AAEb;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"worldToScreen.js","sources":["../../../src/ui/dom/worldToScreen.ts"],"sourcesContent":["/** A layer's transform as drawn: `stage.world` (a Pixi container) fits it, with the camera and its shake applied. */\nexport interface LayerTransform {\n readonly x: number;\n readonly y: number;\n readonly scale: { readonly x: number; readonly y: number };\n}\n\n/**\n * Where a world point is on the overlay, in CSS pixels: through the world layer's own transform (so the camera's\n * zoom, scroll and shake are all in it), times how much CSS stretches the canvas (`cssScale`, 1 normally). Writes\n * into `out`: called for every placed element every frame.\n */\nexport function worldToScreen(layer: LayerTransform, wx: number, wy: number, out: { x: number; y: number }, cssScale = 1): { x: number; y: number } {\n out.x = (layer.x + wx * layer.scale.x) * cssScale;\n out.y = (layer.y + wy * layer.scale.y) * cssScale;\n return out;\n}\n\n/** Whether a point is on a `width` × `height` screen, or within `margin` of it (an element's own size past the point). */\nexport function onScreen(x: number, y: number, width: number, height: number, margin = 0): boolean {\n return x >= -margin && y >= -margin && x <= width + margin && y <= height + margin;\n}\n"],"names":["worldToScreen","layer","wx","wy","out","cssScale","onScreen","x","y","width","height","margin"],"mappings":"AAYO,SAASA,EAAcC,GAAuBC,GAAYC,GAAYC,GAA+BC,IAAW,GAA6B;AAClJ,SAAAD,EAAI,KAAKH,EAAM,IAAIC,IAAKD,EAAM,MAAM,KAAKI,GACzCD,EAAI,KAAKH,EAAM,IAAIE,IAAKF,EAAM,MAAM,KAAKI,GAClCD;AACT;AAGO,SAASE,EAASC,GAAWC,GAAWC,GAAeC,GAAgBC,IAAS,GAAY;AACjG,SAAOJ,KAAK,CAACI,KAAUH,KAAK,CAACG,KAAUJ,KAAKE,IAAQE,KAAUH,KAAKE,IAASC;AAC9E;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"anchor.js","sources":["../../../src/ui/layout/anchor.ts"],"sourcesContent":["import { insets, snapPx, type Insets, type Point, type Size } from './geometry';\n\n/** A spot on the screen a HUD piece is pinned to; it stays there whatever the screen's size. */\nexport type ScreenAnchor = 'top-left' | 'top' | 'top-right' | 'left' | 'center' | 'right' | 'bottom-left' | 'bottom' | 'bottom-right';\n\n/** Fractions of the free space for each anchor: 0 hugs the left/top edge, 1 the right/bottom, 0.5 centres. */\nconst FRACTIONS: Record<ScreenAnchor, readonly [number, number]> = {\n 'top-left': [0, 0],\n top: [0.5, 0],\n 'top-right': [1, 0],\n left: [0, 0.5],\n center: [0.5, 0.5],\n right: [1, 0.5],\n 'bottom-left': [0, 1],\n bottom: [0.5, 1],\n 'bottom-right': [1, 1],\n};\n\n/**\n * Top-left of a box of `size` pinned to `anchor` of the viewport, `margin` in from the edges (a number, or per side\n * for a phone's notch). Centred anchors ignore the margins of the sides they do not touch.\n */\nexport function anchorPosition(anchor: ScreenAnchor, size: Size, viewport: Size, margin: number | Partial<Insets> = 0, snap = false): Point {\n const m = insets(margin);\n const [fx, fy] = FRACTIONS[anchor];\n const x = fx === 0 ? m.left : fx === 1 ? viewport.width - m.right - size.width : (viewport.width - size.width) / 2;\n const y = fy === 0 ? m.top : fy === 1 ? viewport.height - m.bottom - size.height : (viewport.height - size.height) / 2;\n return { x: snapPx(x, snap), y: snapPx(y, snap) };\n}\n"],"names":["FRACTIONS","anchorPosition","anchor","size","viewport","margin","snap","m","insets","fx","fy","x","y","snapPx"],"mappings":";AAMA,MAAMA,IAA6D;AAAA,EACjE,YAAY,CAAC,GAAG,CAAC;AAAA,EACjB,KAAK,CAAC,KAAK,CAAC;AAAA,EACZ,aAAa,CAAC,GAAG,CAAC;AAAA,EAClB,MAAM,CAAC,GAAG,GAAG;AAAA,EACb,QAAQ,CAAC,KAAK,GAAG;AAAA,EACjB,OAAO,CAAC,GAAG,GAAG;AAAA,EACd,eAAe,CAAC,GAAG,CAAC;AAAA,EACpB,QAAQ,CAAC,KAAK,CAAC;AAAA,EACf,gBAAgB,CAAC,GAAG,CAAC;AACvB;AAMO,SAASC,EAAeC,GAAsBC,GAAYC,GAAgBC,IAAmC,GAAGC,IAAO,IAAc;AAC1I,QAAMC,IAAIC,EAAOH,CAAM,GACjB,CAACI,GAAIC,CAAE,IAAIV,EAAUE,CAAM,GAC3BS,IAAIF,MAAO,IAAIF,EAAE,OAAOE,MAAO,IAAIL,EAAS,QAAQG,EAAE,QAAQJ,EAAK,SAASC,EAAS,QAAQD,EAAK,SAAS,GAC3GS,IAAIF,MAAO,IAAIH,EAAE,MAAMG,MAAO,IAAIN,EAAS,SAASG,EAAE,SAASJ,EAAK,UAAUC,EAAS,SAASD,EAAK,UAAU;AACrH,SAAO,EAAE,GAAGU,EAAOF,GAAGL,CAAI,GAAG,GAAGO,EAAOD,GAAGN,CAAI,EAAA;AAChD;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"barDrain.js","sources":["../../../src/ui/layout/barDrain.ts"],"sourcesContent":["/**\n * A bar's two lengths: what it holds now (`value`) and the trail behind it (`trail`) that drains down to it after a\n * short hold, so a hit shows how much it took. Fractions 0–1. Mutated in place: a health bar is set every frame.\n */\nexport interface DrainState {\n value: number;\n trail: number;\n /** What is left of the hold before the trail starts to drain (ms). */\n holdMs: number;\n}\n\nexport interface DrainOptions {\n /** How long the trail holds after a loss before draining (default 400 ms). */\n delayMs?: number;\n /** How fast it drains, in whole bars a second (default 0.6). */\n rate?: number;\n}\n\nexport function drainState(value = 1): DrainState {\n return { value, trail: value, holdMs: 0 };\n}\n\n/**\n * Sets the bar. A loss restarts the hold, so blows in quick succession show one trail for all of them; a gain fills\n * at once and pulls the trail up with it (a heal has nothing to show behind it).\n */\nexport function setDrainValue(state: DrainState, value: number, options: DrainOptions = {}): void {\n const v = value < 0 ? 0 : value > 1 ? 1 : value;\n if (v < state.value) state.holdMs = options.delayMs ?? 400;\n if (v > state.trail) state.trail = v;\n state.value = v;\n}\n\n/** Advances the trail by a frame `dtMs` long. */\nexport function stepDrain(state: DrainState, dtMs: number, options: DrainOptions = {}): void {\n if (state.trail <= state.value) {\n state.trail = state.value;\n return;\n }\n let dt = dtMs;\n if (state.holdMs > 0) {\n const used = Math.min(state.holdMs, dt);\n state.holdMs -= used;\n dt -= used;\n }\n if (dt <= 0) return;\n state.trail = Math.max(state.value, state.trail - ((options.rate ?? 0.6) * dt) / 1000);\n}\n"],"names":["drainState","value","setDrainValue","state","options","v","stepDrain","dtMs","dt","used"],"mappings":"AAkBO,SAASA,EAAWC,IAAQ,GAAe;AAChD,SAAO,EAAE,OAAAA,GAAO,OAAOA,GAAO,QAAQ,EAAA;AACxC;AAMO,SAASC,EAAcC,GAAmBF,GAAeG,IAAwB,CAAA,GAAU;AAChG,QAAMC,IAAIJ,IAAQ,IAAI,IAAIA,IAAQ,IAAI,IAAIA;AAC1C,EAAII,IAAIF,EAAM,UAAOA,EAAM,SAASC,EAAQ,WAAW,MACnDC,IAAIF,EAAM,UAAOA,EAAM,QAAQE,IACnCF,EAAM,QAAQE;AAChB;AAGO,SAASC,EAAUH,GAAmBI,GAAcH,IAAwB,CAAA,GAAU;AAC3F,MAAID,EAAM,SAASA,EAAM,OAAO;AAC9B,IAAAA,EAAM,QAAQA,EAAM;AACpB;AAAA,EACF;AACA,MAAIK,IAAKD;AACT,MAAIJ,EAAM,SAAS,GAAG;AACpB,UAAMM,IAAO,KAAK,IAAIN,EAAM,QAAQK,CAAE;AACtC,IAAAL,EAAM,UAAUM,GAChBD,KAAMC;AAAA,EACR;AACA,EAAID,KAAM,MACVL,EAAM,QAAQ,KAAK,IAAIA,EAAM,OAAOA,EAAM,SAAUC,EAAQ,QAAQ,OAAOI,IAAM,GAAI;AACvF;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"fitToViewport.js","sources":["../../../src/ui/layout/fitToViewport.ts"],"sourcesContent":["import { clamp, snapPx, type Rect, type Size } from './geometry';\n\nexport interface FitOptions {\n /** Kept clear between the window and the screen's edges (default 8). */\n margin?: number;\n /**\n * The scale the window is meant to be drawn at (default 1): a player's \"window size\" setting, or a whole multiple\n * for pixel art. The window is laid out on a box `1 / scale` the screen and drawn back at `scale`.\n */\n scale?: number;\n /**\n * The smallest scale it may shrink to before it starts scrolling instead (default: `scale`, never shrinks). A\n * pixel-art game keeps it at its whole scale: a fractional one smears every texel.\n */\n minScale?: number;\n /** Centre it on the screen instead of keeping `rect.x/y` (default false). */\n centre?: boolean;\n snap?: boolean;\n}\n\nexport interface FitResult extends Rect {\n /** The scale to draw the window at; `x`, `y` are screen pixels, `width`, `height` the window's own units. */\n scale: number;\n /** The window was cut shorter (narrower) than it wanted: its body must scroll. */\n scrollY: boolean;\n scrollX: boolean;\n}\n\n/**\n * Fits a window into the screen so it never overflows it: shrunk down to `minScale` if that is allowed, then cut to\n * what is left (the caller scrolls its body), and moved back inside the edges. `rect` is where and how big the\n * window wants to be, in its own units; `x`, `y` are screen pixels.\n */\nexport function fitToViewport(rect: Rect | Size, viewport: Size, options: FitOptions = {}): FitResult {\n const margin = options.margin ?? 8;\n const preferred = options.scale ?? 1;\n const minScale = Math.min(options.minScale ?? preferred, preferred);\n const snap = options.snap ?? false;\n const availW = Math.max(0, viewport.width - 2 * margin);\n const availH = Math.max(0, viewport.height - 2 * margin);\n const fits = rect.width > 0 && rect.height > 0 ? Math.min(availW / rect.width, availH / rect.height) : preferred;\n const scale = clamp(fits, minScale, preferred);\n // what still does not fit at that scale is cut, and the body scrolls\n const width = Math.min(rect.width, availW / scale);\n const height = Math.min(rect.height, availH / scale);\n const drawnW = width * scale;\n const drawnH = height * scale;\n const wantX = options.centre || !('x' in rect) ? (viewport.width - drawnW) / 2 : rect.x;\n const wantY = options.centre || !('y' in rect) ? (viewport.height - drawnH) / 2 : rect.y;\n const x = clamp(wantX, margin, Math.max(margin, viewport.width - margin - drawnW));\n const y = clamp(wantY, margin, Math.max(margin, viewport.height - margin - drawnH));\n return {\n x: snapPx(x, snap),\n y: snapPx(y, snap),\n width: snap ? Math.floor(width) : width,\n height: snap ? Math.floor(height) : height,\n scale,\n scrollX: width < rect.width,\n scrollY: height < rect.height,\n };\n}\n"],"names":["fitToViewport","rect","viewport","options","margin","preferred","minScale","snap","availW","availH","fits","scale","clamp","width","height","drawnW","drawnH","wantX","wantY","x","y","snapPx"],"mappings":";AAiCO,SAASA,EAAcC,GAAmBC,GAAgBC,IAAsB,CAAA,GAAe;AACpG,QAAMC,IAASD,EAAQ,UAAU,GAC3BE,IAAYF,EAAQ,SAAS,GAC7BG,IAAW,KAAK,IAAIH,EAAQ,YAAYE,GAAWA,CAAS,GAC5DE,IAAOJ,EAAQ,QAAQ,IACvBK,IAAS,KAAK,IAAI,GAAGN,EAAS,QAAQ,IAAIE,CAAM,GAChDK,IAAS,KAAK,IAAI,GAAGP,EAAS,SAAS,IAAIE,CAAM,GACjDM,IAAOT,EAAK,QAAQ,KAAKA,EAAK,SAAS,IAAI,KAAK,IAAIO,IAASP,EAAK,OAAOQ,IAASR,EAAK,MAAM,IAAII,GACjGM,IAAQC,EAAMF,GAAMJ,GAAUD,CAAS,GAEvCQ,IAAQ,KAAK,IAAIZ,EAAK,OAAOO,IAASG,CAAK,GAC3CG,IAAS,KAAK,IAAIb,EAAK,QAAQQ,IAASE,CAAK,GAC7CI,IAASF,IAAQF,GACjBK,IAASF,IAASH,GAClBM,IAAQd,EAAQ,UAAU,EAAE,OAAOF,MAASC,EAAS,QAAQa,KAAU,IAAId,EAAK,GAChFiB,IAAQf,EAAQ,UAAU,EAAE,OAAOF,MAASC,EAAS,SAASc,KAAU,IAAIf,EAAK,GACjFkB,IAAIP,EAAMK,GAAOb,GAAQ,KAAK,IAAIA,GAAQF,EAAS,QAAQE,IAASW,CAAM,CAAC,GAC3EK,IAAIR,EAAMM,GAAOd,GAAQ,KAAK,IAAIA,GAAQF,EAAS,SAASE,IAASY,CAAM,CAAC;AAClF,SAAO;AAAA,IACL,GAAGK,EAAOF,GAAGZ,CAAI;AAAA,IACjB,GAAGc,EAAOD,GAAGb,CAAI;AAAA,IACjB,OAAOA,IAAO,KAAK,MAAMM,CAAK,IAAIA;AAAA,IAClC,QAAQN,IAAO,KAAK,MAAMO,CAAM,IAAIA;AAAA,IACpC,OAAAH;AAAA,IACA,SAASE,IAAQZ,EAAK;AAAA,IACtB,SAASa,IAASb,EAAK;AAAA,EAAA;AAE3B;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"focusNav.js","sources":["../../../src/ui/layout/focusNav.ts"],"sourcesContent":["import type { Rect } from './geometry';\n\nexport type NavDirection = 'up' | 'down' | 'left' | 'right';\n\n/** How much a step off the line of travel costs against a step along it: favours the item straight ahead. */\nconst CROSS_WEIGHT = 2;\n\nfunction cx(r: Rect): number {\n return r.x + r.width / 2;\n}\n\nfunction cy(r: Rect): number {\n return r.y + r.height / 2;\n}\n\n/** The item a fresh focus lands on: the top-most, then left-most. */\nexport function firstFocus(rects: readonly Rect[]): number {\n let best = -1;\n for (let i = 0; i < rects.length; i++) {\n const r = rects[i]!;\n const b = best < 0 ? null : rects[best]!;\n if (!b || r.y < b.y || (r.y === b.y && r.x < b.x)) best = i;\n }\n return best;\n}\n\n/**\n * Spatial navigation (a gamepad's d-pad, the arrow keys): from `current`, the nearest item whose centre lies in\n * `dir`, an item straight ahead preferred over a nearer one off to the side. With `wrap`, going past the last item\n * comes back in from the far side on the same line. Returns `current` when nothing lies that way.\n */\nexport function nextFocus(rects: readonly Rect[], current: number, dir: NavDirection, wrap = false): number {\n if (rects.length === 0) return -1;\n const from = rects[current];\n if (!from) return firstFocus(rects);\n const fx = cx(from);\n const fy = cy(from);\n const horizontal = dir === 'left' || dir === 'right';\n const sign = dir === 'right' || dir === 'down' ? 1 : -1;\n let best = -1;\n let bestScore = Infinity;\n // wrapping: the item farthest the other way, closest to the line\n let back = -1;\n let backScore = Infinity;\n for (let i = 0; i < rects.length; i++) {\n if (i === current) continue;\n const r = rects[i]!;\n const along = ((horizontal ? cx(r) - fx : cy(r) - fy) * sign);\n const across = Math.abs(horizontal ? cy(r) - fy : cx(r) - fx);\n if (along > 0.5) {\n const score = along + CROSS_WEIGHT * across;\n if (score < bestScore) {\n bestScore = score;\n best = i;\n }\n } else if (wrap && along < -0.5) {\n const score = along + CROSS_WEIGHT * across;\n if (score < backScore) {\n backScore = score;\n back = i;\n }\n }\n }\n if (best >= 0) return best;\n return back >= 0 ? back : current;\n}\n\n/** Tab order: the next (`step` 1) or previous (−1) of `count` items, wrapping round. */\nexport function linearFocus(count: number, current: number, step: 1 | -1): number {\n if (count === 0) return -1;\n if (current < 0 || current >= count) return step > 0 ? 0 : count - 1;\n return (current + step + count) % count;\n}\n"],"names":["cx","r","cy","firstFocus","rects","best","i","b","nextFocus","current","dir","wrap","from","fx","fy","horizontal","sign","bestScore","back","backScore","along","across","score","linearFocus","count","step"],"mappings":"AAOA,SAASA,EAAGC,GAAiB;AAC3B,SAAOA,EAAE,IAAIA,EAAE,QAAQ;AACzB;AAEA,SAASC,EAAGD,GAAiB;AAC3B,SAAOA,EAAE,IAAIA,EAAE,SAAS;AAC1B;AAGO,SAASE,EAAWC,GAAgC;AACzD,MAAIC,IAAO;AACX,WAASC,IAAI,GAAGA,IAAIF,EAAM,QAAQE,KAAK;AACrC,UAAML,IAAIG,EAAME,CAAC,GACXC,IAAIF,IAAO,IAAI,OAAOD,EAAMC,CAAI;AACtC,KAAI,CAACE,KAAKN,EAAE,IAAIM,EAAE,KAAMN,EAAE,MAAMM,EAAE,KAAKN,EAAE,IAAIM,EAAE,OAAIF,IAAOC;AAAA,EAC5D;AACA,SAAOD;AACT;AAOO,SAASG,EAAUJ,GAAwBK,GAAiBC,GAAmBC,IAAO,IAAe;AAC1G,MAAIP,EAAM,WAAW,EAAG,QAAO;AAC/B,QAAMQ,IAAOR,EAAMK,CAAO;AAC1B,MAAI,CAACG,EAAM,QAAOT,EAAWC,CAAK;AAClC,QAAMS,IAAKb,EAAGY,CAAI,GACZE,IAAKZ,EAAGU,CAAI,GACZG,IAAaL,MAAQ,UAAUA,MAAQ,SACvCM,IAAON,MAAQ,WAAWA,MAAQ,SAAS,IAAI;AACrD,MAAIL,IAAO,IACPY,IAAY,OAEZC,IAAO,IACPC,IAAY;AAChB,WAASb,IAAI,GAAGA,IAAIF,EAAM,QAAQE,KAAK;AACrC,QAAIA,MAAMG,EAAS;AACnB,UAAMR,IAAIG,EAAME,CAAC,GACXc,KAAUL,IAAaf,EAAGC,CAAC,IAAIY,IAAKX,EAAGD,CAAC,IAAIa,KAAME,GAClDK,IAAS,KAAK,IAAIN,IAAab,EAAGD,CAAC,IAAIa,IAAKd,EAAGC,CAAC,IAAIY,CAAE;AAC5D,QAAIO,IAAQ,KAAK;AACf,YAAME,IAAQF,IAAQ,IAAeC;AACrC,MAAIC,IAAQL,MACVA,IAAYK,GACZjB,IAAOC;AAAA,IAEX,WAAWK,KAAQS,IAAQ,MAAM;AAC/B,YAAME,IAAQF,IAAQ,IAAeC;AACrC,MAAIC,IAAQH,MACVA,IAAYG,GACZJ,IAAOZ;AAAA,IAEX;AAAA,EACF;AACA,SAAID,KAAQ,IAAUA,IACfa,KAAQ,IAAIA,IAAOT;AAC5B;AAGO,SAASc,EAAYC,GAAef,GAAiBgB,GAAsB;AAChF,SAAID,MAAU,IAAU,KACpBf,IAAU,KAAKA,KAAWe,IAAcC,IAAO,IAAI,IAAID,IAAQ,KAC3Df,IAAUgB,IAAOD,KAASA;AACpC;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"geometry.js","sources":["../../../src/ui/layout/geometry.ts"],"sourcesContent":["/**\n * The shapes every UI layout function speaks: plain objects, no Pixi and no DOM, so the same maths places a canvas\n * widget, a DOM panel or a React component.\n */\nexport interface Size {\n width: number;\n height: number;\n}\n\nexport interface Point {\n x: number;\n y: number;\n}\n\nexport interface Rect extends Point, Size {}\n\n/** Space kept clear on each side (padding, margins, a phone's safe area). */\nexport interface Insets {\n top: number;\n right: number;\n bottom: number;\n left: number;\n}\n\n/** `8` or `{ top: 8, left: 4 }` → all four sides; a missing side is 0. */\nexport function insets(v: number | Partial<Insets> | undefined): Insets {\n if (v === undefined) return { top: 0, right: 0, bottom: 0, left: 0 };\n if (typeof v === 'number') return { top: v, right: v, bottom: v, left: v };\n return { top: v.top ?? 0, right: v.right ?? 0, bottom: v.bottom ?? 0, left: v.left ?? 0 };\n}\n\n/**\n * Rounds to a whole pixel when `on`. Pixel art laid out at x.5 lands each texel across two screen pixels and reads\n * blurred or a pixel wider on one side, so a pixel-perfect game snaps every UI position and size.\n */\nexport function snapPx(v: number, on = true): number {\n return on ? Math.round(v) : v;\n}\n\n/**\n * A rect with its edges (not its size) rounded: two neighbours that touch before snapping still touch after, where\n * rounding x and width apart could open or overlap a pixel between them.\n */\nexport function snapRect(r: Rect, on = true): Rect {\n if (!on) return { x: r.x, y: r.y, width: r.width, height: r.height };\n const x = Math.round(r.x);\n const y = Math.round(r.y);\n return { x, y, width: Math.round(r.x + r.width) - x, height: Math.round(r.y + r.height) - y };\n}\n\nexport function clamp(v: number, min: number, max: number): number {\n return v < min ? min : v > max ? max : v;\n}\n"],"names":["insets","v","snapPx","on","snapRect","r","x","y","clamp","min","max"],"mappings":"AAyBO,SAASA,EAAOC,GAAiD;AACtE,SAAIA,MAAM,SAAkB,EAAE,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,EAAA,IAC7D,OAAOA,KAAM,WAAiB,EAAE,KAAKA,GAAG,OAAOA,GAAG,QAAQA,GAAG,MAAMA,EAAA,IAChE,EAAE,KAAKA,EAAE,OAAO,GAAG,OAAOA,EAAE,SAAS,GAAG,QAAQA,EAAE,UAAU,GAAG,MAAMA,EAAE,QAAQ,EAAA;AACxF;AAMO,SAASC,EAAOD,GAAWE,IAAK,IAAc;AACnD,SAAOA,IAAK,KAAK,MAAMF,CAAC,IAAIA;AAC9B;AAMO,SAASG,EAASC,GAASF,IAAK,IAAY;AACjD,MAAI,CAACA,EAAI,QAAO,EAAE,GAAGE,EAAE,GAAG,GAAGA,EAAE,GAAG,OAAOA,EAAE,OAAO,QAAQA,EAAE,OAAA;AAC5D,QAAMC,IAAI,KAAK,MAAMD,EAAE,CAAC,GAClBE,IAAI,KAAK,MAAMF,EAAE,CAAC;AACxB,SAAO,EAAE,GAAAC,GAAG,GAAAC,GAAG,OAAO,KAAK,MAAMF,EAAE,IAAIA,EAAE,KAAK,IAAIC,GAAG,QAAQ,KAAK,MAAMD,EAAE,IAAIA,EAAE,MAAM,IAAIE,EAAA;AAC5F;AAEO,SAASC,EAAMP,GAAWQ,GAAaC,GAAqB;AACjE,SAAOT,IAAIQ,IAAMA,IAAMR,IAAIS,IAAMA,IAAMT;AACzC;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"listWidth.js","sources":["../../../src/ui/layout/listWidth.ts"],"sourcesContent":["export interface ListWidthOptions {\n /** Added to the longest label: the row's padding, a check mark, a scroll bar (default 0). */\n padding?: number;\n min?: number;\n max?: number;\n}\n\n/**\n * How wide a drop-down list (or a menu) must be so its longest label fits: measured, not guessed, so no label is\n * clipped. `measure` is the caller's (a canvas `measureText`, a bitmap font's advance sum). Rounded up to a whole\n * pixel so the measurement never loses the last half pixel of a letter.\n */\nexport function listWidth(labels: readonly string[], measure: (label: string) => number, options: ListWidthOptions = {}): number {\n let widest = 0;\n for (const l of labels) widest = Math.max(widest, measure(l));\n const w = Math.ceil(widest + (options.padding ?? 0));\n return Math.min(options.max ?? Infinity, Math.max(options.min ?? 0, w));\n}\n"],"names":["listWidth","labels","measure","options","widest","l","w"],"mappings":"AAYO,SAASA,EAAUC,GAA2BC,GAAoCC,IAA4B,CAAA,GAAY;AAC/H,MAAIC,IAAS;AACb,aAAWC,KAAKJ,EAAQ,CAAAG,IAAS,KAAK,IAAIA,GAAQF,EAAQG,CAAC,CAAC;AAC5D,QAAMC,IAAI,KAAK,KAAKF,KAAUD,EAAQ,WAAW,EAAE;AACnD,SAAO,KAAK,IAAIA,EAAQ,OAAO,OAAU,KAAK,IAAIA,EAAQ,OAAO,GAAGG,CAAC,CAAC;AACxE;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"placeTooltip.js","sources":["../../../src/ui/layout/placeTooltip.ts"],"sourcesContent":["import { clamp, snapPx, type Point, type Rect, type Size } from './geometry';\n\nexport type TipSide = 'below' | 'above' | 'right' | 'left';\n\nexport interface TooltipOptions {\n /** The side tried first (default 'below'); the opposite one when it does not fit. */\n side?: TipSide;\n /** Between the anchor and the tip (default 4). */\n gap?: number;\n /** Kept clear of the screen's edges (default 4). */\n margin?: number;\n /** Across the side: line up with the anchor's start or centre on it (default 'start'). */\n align?: 'start' | 'center';\n snap?: boolean;\n}\n\nexport interface TooltipPlacement extends Point {\n /** The side it went on after flipping. */\n side: TipSide;\n}\n\nconst OPPOSITE: Record<TipSide, TipSide> = { below: 'above', above: 'below', right: 'left', left: 'right' };\n\n/** Room on `side` of the anchor, inside the margins. */\nfunction room(side: TipSide, a: Rect, view: Size, gap: number, margin: number): number {\n switch (side) {\n case 'below':\n return view.height - margin - (a.y + a.height + gap);\n case 'above':\n return a.y - gap - margin;\n case 'right':\n return view.width - margin - (a.x + a.width + gap);\n case 'left':\n return a.x - gap - margin;\n }\n}\n\n/**\n * Where a tooltip of `tip` size goes beside `anchor`: on the preferred side when it fits, flipped to the opposite one\n * when it does not, the roomier of the two when neither does; then slid along the edge to stay on screen.\n */\nexport function placeTooltip(anchor: Rect, tip: Size, viewport: Size, options: TooltipOptions = {}): TooltipPlacement {\n const gap = options.gap ?? 4;\n const margin = options.margin ?? 4;\n const want = options.side ?? 'below';\n const vertical = want === 'below' || want === 'above';\n const need = vertical ? tip.height : tip.width;\n let side = want;\n if (room(want, anchor, viewport, gap, margin) < need) {\n const other = OPPOSITE[want];\n const otherRoom = room(other, anchor, viewport, gap, margin);\n if (otherRoom >= need || otherRoom > room(want, anchor, viewport, gap, margin)) side = other;\n }\n let x: number;\n let y: number;\n if (vertical) {\n y = side === 'below' ? anchor.y + anchor.height + gap : anchor.y - gap - tip.height;\n x = options.align === 'center' ? anchor.x + (anchor.width - tip.width) / 2 : anchor.x;\n } else {\n x = side === 'right' ? anchor.x + anchor.width + gap : anchor.x - gap - tip.width;\n y = options.align === 'center' ? anchor.y + (anchor.height - tip.height) / 2 : anchor.y;\n }\n x = clamp(x, margin, Math.max(margin, viewport.width - margin - tip.width));\n y = clamp(y, margin, Math.max(margin, viewport.height - margin - tip.height));\n return { x: snapPx(x, options.snap ?? false), y: snapPx(y, options.snap ?? false), side };\n}\n"],"names":["OPPOSITE","room","side","a","view","gap","margin","placeTooltip","anchor","tip","viewport","options","want","vertical","need","other","otherRoom","x","y","clamp","snapPx"],"mappings":";AAqBA,MAAMA,IAAqC,EAAE,OAAO,SAAS,OAAO,SAAS,OAAO,QAAQ,MAAM,QAAA;AAGlG,SAASC,EAAKC,GAAeC,GAASC,GAAYC,GAAaC,GAAwB;AACrF,UAAQJ,GAAA;AAAA,IACN,KAAK;AACH,aAAOE,EAAK,SAASE,KAAUH,EAAE,IAAIA,EAAE,SAASE;AAAA,IAClD,KAAK;AACH,aAAOF,EAAE,IAAIE,IAAMC;AAAA,IACrB,KAAK;AACH,aAAOF,EAAK,QAAQE,KAAUH,EAAE,IAAIA,EAAE,QAAQE;AAAA,IAChD,KAAK;AACH,aAAOF,EAAE,IAAIE,IAAMC;AAAA,EAAA;AAEzB;AAMO,SAASC,EAAaC,GAAcC,GAAWC,GAAgBC,IAA0B,CAAA,GAAsB;AACpH,QAAMN,IAAMM,EAAQ,OAAO,GACrBL,IAASK,EAAQ,UAAU,GAC3BC,IAAOD,EAAQ,QAAQ,SACvBE,IAAWD,MAAS,WAAWA,MAAS,SACxCE,IAAOD,IAAWJ,EAAI,SAASA,EAAI;AACzC,MAAIP,IAAOU;AACX,MAAIX,EAAKW,GAAMJ,GAAQE,GAAUL,GAAKC,CAAM,IAAIQ,GAAM;AACpD,UAAMC,IAAQf,EAASY,CAAI,GACrBI,IAAYf,EAAKc,GAAOP,GAAQE,GAAUL,GAAKC,CAAM;AAC3D,KAAIU,KAAaF,KAAQE,IAAYf,EAAKW,GAAMJ,GAAQE,GAAUL,GAAKC,CAAM,OAAGJ,IAAOa;AAAA,EACzF;AACA,MAAIE,GACAC;AACJ,SAAIL,KACFK,IAAIhB,MAAS,UAAUM,EAAO,IAAIA,EAAO,SAASH,IAAMG,EAAO,IAAIH,IAAMI,EAAI,QAC7EQ,IAAIN,EAAQ,UAAU,WAAWH,EAAO,KAAKA,EAAO,QAAQC,EAAI,SAAS,IAAID,EAAO,MAEpFS,IAAIf,MAAS,UAAUM,EAAO,IAAIA,EAAO,QAAQH,IAAMG,EAAO,IAAIH,IAAMI,EAAI,OAC5ES,IAAIP,EAAQ,UAAU,WAAWH,EAAO,KAAKA,EAAO,SAASC,EAAI,UAAU,IAAID,EAAO,IAExFS,IAAIE,EAAMF,GAAGX,GAAQ,KAAK,IAAIA,GAAQI,EAAS,QAAQJ,IAASG,EAAI,KAAK,CAAC,GAC1ES,IAAIC,EAAMD,GAAGZ,GAAQ,KAAK,IAAIA,GAAQI,EAAS,SAASJ,IAASG,EAAI,MAAM,CAAC,GACrE,EAAE,GAAGW,EAAOH,GAAGN,EAAQ,QAAQ,EAAK,GAAG,GAAGS,EAAOF,GAAGP,EAAQ,QAAQ,EAAK,GAAG,MAAAT,EAAA;AACrF;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"scroll.js","sources":["../../../src/ui/layout/scroll.ts"],"sourcesContent":["import { clamp } from './geometry';\n\n/** How far content of `content` length can scroll in a window of `viewport` length. */\nexport function maxScroll(content: number, viewport: number): number {\n return Math.max(0, content - viewport);\n}\n\n/** A scroll offset kept between the top and the last full window of content. */\nexport function clampScroll(offset: number, content: number, viewport: number): number {\n return clamp(offset, 0, maxScroll(content, viewport));\n}\n\n/**\n * The smallest scroll that brings an item (`start`, `size` along the scroll axis) fully into view: a list moved by\n * the keyboard follows the focus without jumping when the focus is already visible.\n */\nexport function scrollIntoView(offset: number, start: number, size: number, viewport: number, content: number): number {\n let next = offset;\n if (start < next) next = start;\n else if (start + size > next + viewport) next = start + size - viewport;\n return clampScroll(next, content, viewport);\n}\n\n/** Below this speed (px a second) a fling has stopped. */\nexport const FLING_STOP = 8;\n\n/**\n * A fling's speed after `dtMs`, losing `friction` of it every 60th of a second: the same glide at any frame rate.\n * 0 once it falls under `FLING_STOP`.\n */\nexport function stepFling(velocity: number, dtMs: number, friction = 0.06): number {\n const v = velocity * Math.pow(1 - friction, dtMs / (1000 / 60));\n return Math.abs(v) < FLING_STOP ? 0 : v;\n}\n"],"names":["maxScroll","content","viewport","clampScroll","offset","clamp","scrollIntoView","start","size","next","FLING_STOP","stepFling","velocity","dtMs","friction","v"],"mappings":";AAGO,SAASA,EAAUC,GAAiBC,GAA0B;AACnE,SAAO,KAAK,IAAI,GAAGD,IAAUC,CAAQ;AACvC;AAGO,SAASC,EAAYC,GAAgBH,GAAiBC,GAA0B;AACrF,SAAOG,EAAMD,GAAQ,GAAGJ,EAAUC,GAASC,CAAQ,CAAC;AACtD;AAMO,SAASI,EAAeF,GAAgBG,GAAeC,GAAcN,GAAkBD,GAAyB;AACrH,MAAIQ,IAAOL;AACX,SAAIG,IAAQE,IAAMA,IAAOF,IAChBA,IAAQC,IAAOC,IAAOP,MAAUO,IAAOF,IAAQC,IAAON,IACxDC,EAAYM,GAAMR,GAASC,CAAQ;AAC5C;AAGO,MAAMQ,IAAa;AAMnB,SAASC,EAAUC,GAAkBC,GAAcC,IAAW,MAAc;AACjF,QAAMC,IAAIH,IAAW,KAAK,IAAI,IAAIE,GAAUD,IAAQ,kBAAU;AAC9D,SAAO,KAAK,IAAIE,CAAC,IAAIL,IAAa,IAAIK;AACxC;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"slider.js","sources":["../../../src/ui/layout/slider.ts"],"sourcesContent":["import { clamp } from './geometry';\n\n/** A value held to the range and to the nearest `step` above `min` (no step: any value). */\nexport function quantize(value: number, min: number, max: number, step = 0): number {\n const v = clamp(value, min, max);\n if (step <= 0) return v;\n const q = min + Math.round((v - min) / step) * step;\n // the last step may overshoot max when the range is not a whole number of steps\n return clamp(Number(q.toFixed(10)), min, max);\n}\n\n/** Where `value` sits along the range, 0–1. */\nexport function sliderFraction(value: number, min: number, max: number): number {\n return max > min ? clamp((value - min) / (max - min), 0, 1) : 0;\n}\n\n/** The value under a pointer at `px` on a track starting at `trackStart`, `trackLength` long. */\nexport function sliderValueAt(px: number, trackStart: number, trackLength: number, min: number, max: number, step = 0): number {\n const f = trackLength > 0 ? clamp((px - trackStart) / trackLength, 0, 1) : 0;\n return quantize(min + f * (max - min), min, max, step);\n}\n"],"names":["quantize","value","min","max","step","v","clamp","q","sliderFraction","sliderValueAt","px","trackStart","trackLength"],"mappings":";AAGO,SAASA,EAASC,GAAeC,GAAaC,GAAaC,IAAO,GAAW;AAClF,QAAMC,IAAIC,EAAML,GAAOC,GAAKC,CAAG;AAC/B,MAAIC,KAAQ,EAAG,QAAOC;AACtB,QAAME,IAAIL,IAAM,KAAK,OAAOG,IAAIH,KAAOE,CAAI,IAAIA;AAE/C,SAAOE,EAAM,OAAOC,EAAE,QAAQ,EAAE,CAAC,GAAGL,GAAKC,CAAG;AAC9C;AAGO,SAASK,EAAeP,GAAeC,GAAaC,GAAqB;AAC9E,SAAOA,IAAMD,IAAMI,GAAOL,IAAQC,MAAQC,IAAMD,IAAM,GAAG,CAAC,IAAI;AAChE;AAGO,SAASO,EAAcC,GAAYC,GAAoBC,GAAqBV,GAAaC,GAAaC,IAAO,GAAW;AAC7H,QAAM,IAAIQ,IAAc,IAAIN,GAAOI,IAAKC,KAAcC,GAAa,GAAG,CAAC,IAAI;AAC3E,SAAOZ,EAASE,IAAM,KAAKC,IAAMD,IAAMA,GAAKC,GAAKC,CAAI;AACvD;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"stack.js","sources":["../../../src/ui/layout/stack.ts"],"sourcesContent":["import { insets, snapPx, type Insets, type Rect, type Size } from './geometry';\n\nexport type StackDirection = 'vertical' | 'horizontal';\n/** Where items sit across the stack: its start, middle or end, or stretched to the widest (tallest) one. */\nexport type StackAlign = 'start' | 'center' | 'end' | 'stretch';\n\nexport interface StackOptions {\n direction?: StackDirection;\n /** Between two items (default 0). */\n gap?: number;\n /** Inside the stack's edge (default 0). */\n padding?: number | Partial<Insets>;\n align?: StackAlign;\n /** A cross size to align in instead of the widest item's (a column as wide as its window). */\n crossSize?: number;\n /** Whole pixels (pixel-perfect UI). */\n snap?: boolean;\n}\n\nexport interface StackResult extends Size {\n /** One rect per item, in the order given, relative to the stack's top-left. */\n items: Rect[];\n}\n\n/**\n * Lays items one after another down (or across) with a gap, inside padding. The stack's size is what the items\n * need, so a parent can lay the stack out in turn. Pure, for canvas widgets and DOM/React layouts alike.\n */\nexport function stackLayout(sizes: readonly Size[], options: StackOptions = {}): StackResult {\n const vertical = (options.direction ?? 'vertical') === 'vertical';\n const gap = options.gap ?? 0;\n const pad = insets(options.padding);\n const align = options.align ?? 'start';\n const snap = options.snap ?? false;\n let cross = 0;\n for (const s of sizes) cross = Math.max(cross, vertical ? s.width : s.height);\n if (options.crossSize !== undefined) cross = options.crossSize;\n const items: Rect[] = [];\n let main = vertical ? pad.top : pad.left;\n const crossStart = vertical ? pad.left : pad.top;\n for (let i = 0; i < sizes.length; i++) {\n const s = sizes[i]!;\n const itemMain = vertical ? s.height : s.width;\n let itemCross = vertical ? s.width : s.height;\n let offset = 0;\n if (align === 'stretch') itemCross = cross;\n else if (align === 'center') offset = (cross - itemCross) / 2;\n else if (align === 'end') offset = cross - itemCross;\n const a = snapPx(main, snap);\n const b = snapPx(crossStart + offset, snap);\n const len = snapPx(itemMain, snap);\n const thick = snapPx(itemCross, snap);\n items.push(vertical ? { x: b, y: a, width: thick, height: len } : { x: a, y: b, width: len, height: thick });\n main += itemMain + (i < sizes.length - 1 ? gap : 0);\n }\n const mainEnd = main + (vertical ? pad.bottom : pad.right);\n const crossEnd = crossStart + cross + (vertical ? pad.right : pad.bottom);\n return {\n items,\n width: snapPx(vertical ? crossEnd : mainEnd, snap),\n height: snapPx(vertical ? mainEnd : crossEnd, snap),\n };\n}\n"],"names":["stackLayout","sizes","options","vertical","gap","pad","insets","align","snap","cross","s","items","main","crossStart","i","itemMain","itemCross","offset","a","snapPx","b","len","thick","mainEnd","crossEnd"],"mappings":";AA4BO,SAASA,EAAYC,GAAwBC,IAAwB,IAAiB;AAC3F,QAAMC,KAAYD,EAAQ,aAAa,gBAAgB,YACjDE,IAAMF,EAAQ,OAAO,GACrBG,IAAMC,EAAOJ,EAAQ,OAAO,GAC5BK,IAAQL,EAAQ,SAAS,SACzBM,IAAON,EAAQ,QAAQ;AAC7B,MAAIO,IAAQ;AACZ,aAAWC,KAAKT,EAAO,CAAAQ,IAAQ,KAAK,IAAIA,GAAON,IAAWO,EAAE,QAAQA,EAAE,MAAM;AAC5E,EAAIR,EAAQ,cAAc,WAAWO,IAAQP,EAAQ;AACrD,QAAMS,IAAgB,CAAA;AACtB,MAAIC,IAAOT,IAAWE,EAAI,MAAMA,EAAI;AACpC,QAAMQ,IAAaV,IAAWE,EAAI,OAAOA,EAAI;AAC7C,WAASS,IAAI,GAAGA,IAAIb,EAAM,QAAQa,KAAK;AACrC,UAAMJ,IAAIT,EAAMa,CAAC,GACXC,IAAWZ,IAAWO,EAAE,SAASA,EAAE;AACzC,QAAIM,IAAYb,IAAWO,EAAE,QAAQA,EAAE,QACnCO,IAAS;AACb,IAAIV,MAAU,YAAWS,IAAYP,IAC5BF,MAAU,WAAUU,KAAUR,IAAQO,KAAa,IACnDT,MAAU,UAAOU,IAASR,IAAQO;AAC3C,UAAME,IAAIC,EAAOP,GAAMJ,CAAI,GACrBY,IAAID,EAAON,IAAaI,GAAQT,CAAI,GACpCa,IAAMF,EAAOJ,GAAUP,CAAI,GAC3Bc,IAAQH,EAAOH,GAAWR,CAAI;AACpC,IAAAG,EAAM,KAAKR,IAAW,EAAE,GAAGiB,GAAG,GAAGF,GAAG,OAAOI,GAAO,QAAQD,MAAQ,EAAE,GAAGH,GAAG,GAAGE,GAAG,OAAOC,GAAK,QAAQC,GAAO,GAC3GV,KAAQG,KAAYD,IAAIb,EAAM,SAAS,IAAIG,IAAM;AAAA,EACnD;AACA,QAAMmB,IAAUX,KAAQT,IAAWE,EAAI,SAASA,EAAI,QAC9CmB,IAAWX,IAAaJ,KAASN,IAAWE,EAAI,QAAQA,EAAI;AAClE,SAAO;AAAA,IACL,OAAAM;AAAA,IACA,OAAOQ,EAAOhB,IAAWqB,IAAWD,GAASf,CAAI;AAAA,IACjD,QAAQW,EAAOhB,IAAWoB,IAAUC,GAAUhB,CAAI;AAAA,EAAA;AAEtD;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"wrapRow.js","sources":["../../../src/ui/layout/wrapRow.ts"],"sourcesContent":["/**\n * Breaks a row of items (tabs, chips, buttons) into lines no wider than `maxWidth`: each line holds the indices of\n * its items in order. An item wider than the line still gets a line of its own rather than being dropped.\n */\nexport function wrapRow(widths: readonly number[], maxWidth: number, gap = 0): number[][] {\n const rows: number[][] = [];\n let row: number[] = [];\n let used = 0;\n for (let i = 0; i < widths.length; i++) {\n const w = widths[i]!;\n const next = row.length === 0 ? w : used + gap + w;\n if (row.length > 0 && next > maxWidth) {\n rows.push(row);\n row = [i];\n used = w;\n } else {\n row.push(i);\n used = next;\n }\n }\n if (row.length > 0) rows.push(row);\n return rows;\n}\n"],"names":["wrapRow","widths","maxWidth","gap","rows","row","used","i","w","next"],"mappings":"AAIO,SAASA,EAAQC,GAA2BC,GAAkBC,IAAM,GAAe;AACxF,QAAMC,IAAmB,CAAA;AACzB,MAAIC,IAAgB,CAAA,GAChBC,IAAO;AACX,WAASC,IAAI,GAAGA,IAAIN,EAAO,QAAQM,KAAK;AACtC,UAAMC,IAAIP,EAAOM,CAAC,GACZE,IAAOJ,EAAI,WAAW,IAAIG,IAAIF,IAAOH,IAAMK;AACjD,IAAIH,EAAI,SAAS,KAAKI,IAAOP,KAC3BE,EAAK,KAAKC,CAAG,GACbA,IAAM,CAACE,CAAC,GACRD,IAAOE,MAEPH,EAAI,KAAKE,CAAC,GACVD,IAAOG;AAAA,EAEX;AACA,SAAIJ,EAAI,SAAS,KAAGD,EAAK,KAAKC,CAAG,GAC1BD;AACT;"}
|
package/dist/ui.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ui.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|