UNPKG

kinetic-slider

Version:

A WebGL-powered kinetic slider component using PIXI.js

466 lines (463 loc) 16.2 kB
import { Assets, Rectangle, Texture } from 'pixi.js'; var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); const isDevelopment = false; class AtlasManager { /** * Create a new atlas manager * * @param options - Options for the atlas manager * @param resourceManager - Optional ResourceManager for tracking resources */ constructor(options = {}, resourceManager) { // Loaded atlases __publicField(this, "atlases", /* @__PURE__ */ new Map()); // Atlas textures (the full atlas texture) __publicField(this, "atlasTextures", /* @__PURE__ */ new Map()); // Cached frame textures (individual sprites cut from the atlas) __publicField(this, "frameTextures", /* @__PURE__ */ new Map()); // Image textures (for individual images, used as fallback) __publicField(this, "imageTextures", /* @__PURE__ */ new Map()); // Loading status for atlases __publicField(this, "atlasStatus", /* @__PURE__ */ new Map()); // Loading status for individual images __publicField(this, "imageStatus", /* @__PURE__ */ new Map()); // Reference to ResourceManager for tracking resources __publicField(this, "resourceManager"); // Options for the atlas manager __publicField(this, "options"); this.options = { debug: isDevelopment, preferAtlas: true, cacheFrameTextures: true, basePath: "", ...options }; this.resourceManager = resourceManager; if (this.options.debug) { console.log("AtlasManager initialized with options:", this.options); } } /** * Extract the filename from a path for atlas frame lookup * * @param imagePath - The full path to the image * @returns The filename part of the path */ getFilenameFromPath(imagePath) { if (!imagePath) return ""; const filename = imagePath.split("/").pop() || imagePath; if (this.options.debug) { console.log(`Extracted filename "${filename}" from path "${imagePath}"`); } return filename; } /** * Load a texture atlas from a JSON file * * @param atlasId - Identifier for the atlas * @param jsonUrl - URL to the atlas JSON file * @param imageUrl - Optional URL to the atlas image file (if not specified in JSON) * @returns Promise resolving when the atlas is loaded */ async loadAtlas(atlasId, jsonUrl, imageUrl) { if (this.atlasStatus.get(atlasId) === "loaded" /* Loaded */) { this.log(`Atlas '${atlasId}' already loaded, skipping`); return true; } if (this.atlasStatus.get(atlasId) === "loading" /* Loading */) { this.log(`Atlas '${atlasId}' is already loading, waiting...`); return new Promise((resolve) => { const checkInterval = setInterval(() => { const status = this.atlasStatus.get(atlasId); if (status === "loaded" /* Loaded */) { clearInterval(checkInterval); resolve(true); } else if (status === "failed" /* Failed */) { clearInterval(checkInterval); resolve(false); } }, 100); }); } try { this.atlasStatus.set(atlasId, "loading" /* Loading */); this.log(`Loading atlas '${atlasId}' from ${jsonUrl}`); let atlasData; try { const response = await fetch(jsonUrl); if (!response.ok) { throw new Error(`Failed to load atlas JSON: ${response.statusText}`); } atlasData = await response.json(); } catch (error) { this.log(`Error loading atlas JSON: ${error}`, "error"); this.atlasStatus.set(atlasId, "failed" /* Failed */); return false; } if (!atlasData.frames || !atlasData.meta) { this.log(`Invalid atlas data: missing frames or meta`, "error"); this.atlasStatus.set(atlasId, "failed" /* Failed */); return false; } const atlasImageUrl = imageUrl || (atlasData.meta.image ? this.options.basePath ? `${this.options.basePath}/${atlasData.meta.image}` : atlasData.meta.image : null); if (!atlasImageUrl) { this.log(`Invalid atlas data: no image URL specified`, "error"); this.atlasStatus.set(atlasId, "failed" /* Failed */); return false; } let atlasTexture; try { if (Assets.cache.has(atlasImageUrl)) { atlasTexture = Assets.cache.get(atlasImageUrl); } else { this.log(`Loading atlas texture from ${atlasImageUrl}`); atlasTexture = await Assets.load(atlasImageUrl); } if (this.resourceManager) { this.resourceManager.trackTexture(atlasImageUrl, atlasTexture); } } catch (error) { this.log(`Error loading atlas texture: ${error}`, "error"); this.atlasStatus.set(atlasId, "failed" /* Failed */); return false; } this.atlases.set(atlasId, atlasData); this.atlasTextures.set(atlasId, atlasTexture); this.atlasStatus.set(atlasId, "loaded" /* Loaded */); this.log(`Atlas '${atlasId}' loaded successfully with ${Object.keys(atlasData.frames).length} frames`); return true; } catch (error) { this.log(`Unexpected error loading atlas: ${error}`, "error"); this.atlasStatus.set(atlasId, "failed" /* Failed */); return false; } } /** * Check if a frame exists in any loaded atlas * * @param frameName - Name of the frame to check * @returns The ID of the atlas containing the frame, or null if not found */ hasFrame(frameName) { for (const [atlasId, atlas] of this.atlases.entries()) { if (atlas.frames[frameName]) { return atlasId; } } if (frameName.includes("/")) { const filename = this.getFilenameFromPath(frameName); for (const [atlasId, atlas] of this.atlases.entries()) { if (atlas.frames[filename]) { if (this.options.debug) { console.log(`Found frame "${filename}" in atlas "${atlasId}" by extracting from path "${frameName}"`); } return atlasId; } } } return null; } /** * Get a texture for a frame from an atlas * * @param frameName - Name of the frame * @param atlasId - Optional ID of the atlas to use (if not specified, all atlases will be searched) * @returns The texture for the frame, or null if not found */ getFrameTexture(frameName, atlasId) { const cacheKey = atlasId ? `${atlasId}:${frameName}` : frameName; if (this.options.cacheFrameTextures && this.frameTextures.has(cacheKey)) { return this.frameTextures.get(cacheKey); } let targetAtlasId = atlasId; let frameData = null; let lookupName = frameName; if (frameName.includes("/")) { lookupName = this.getFilenameFromPath(frameName); } if (targetAtlasId) { const atlas = this.atlases.get(targetAtlasId); if (!atlas) { this.log(`Atlas '${targetAtlasId}' not found`, "warn"); return null; } frameData = atlas.frames[frameName] || null; if (!frameData && frameName !== lookupName) { frameData = atlas.frames[lookupName] || null; if (frameData && this.options.debug) { console.log(`Found frame "${lookupName}" in atlas "${targetAtlasId}" by extracting from path "${frameName}"`); } } if (!frameData) { this.log(`Frame '${frameName}' not found in atlas '${targetAtlasId}'`, "warn"); return null; } } else { for (const [id, atlas] of this.atlases.entries()) { if (atlas.frames[frameName]) { targetAtlasId = id; frameData = atlas.frames[frameName]; break; } if (frameName !== lookupName && atlas.frames[lookupName]) { targetAtlasId = id; frameData = atlas.frames[lookupName]; if (this.options.debug) { console.log(`Found frame "${lookupName}" in atlas "${id}" by extracting from path "${frameName}"`); } break; } } if (!targetAtlasId || !frameData) { this.log(`Frame '${frameName}' not found in any atlas`, "warn"); return null; } } const atlasTexture = this.atlasTextures.get(targetAtlasId); if (!atlasTexture) { this.log(`Atlas texture for '${targetAtlasId}' not found`, "warn"); return null; } try { const rect = new Rectangle( frameData.frame.x, frameData.frame.y, frameData.frame.w, frameData.frame.h ); const source = atlasTexture.source; const frameTexture = new Texture({ source, // Use the same source as the atlas texture frame: rect, // The frame rectangle within the atlas orig: frameData.trimmed && frameData.spriteSourceSize ? new Rectangle( frameData.spriteSourceSize.x, frameData.spriteSourceSize.y, frameData.spriteSourceSize.w, frameData.spriteSourceSize.h ) : void 0, trim: frameData.trimmed ? rect : void 0, rotate: frameData.rotated ? 2 : 0 // 2 = DEGREES_90, 0 = DEGREES_0 }); if (this.options.cacheFrameTextures) { this.frameTextures.set(cacheKey, frameTexture); if (this.resourceManager) { } } return frameTexture; } catch (error) { this.log(`Error creating frame texture: ${error}`, "error"); return null; } } /** * Get a list of all frame names in an atlas * * @param atlasId - ID of the atlas * @returns Array of frame names, or empty array if atlas not found */ getFrameNames(atlasId) { const atlas = this.atlases.get(atlasId); if (!atlas) { return []; } return Object.keys(atlas.frames); } /** * Get a texture for an image, either from an atlas or as an individual texture * * @param imagePath - Path to the image * @param atlasId - Optional ID of a specific atlas to check first * @returns Promise resolving to the texture, or null if not found */ async getTexture(imagePath, atlasId) { if (this.options.preferAtlas) { let frameTexture = this.getFrameTexture(imagePath, atlasId); if (!frameTexture && imagePath.includes("/")) { const filename = this.getFilenameFromPath(imagePath); frameTexture = this.getFrameTexture(filename, atlasId); if (frameTexture && this.options.debug) { console.log(`Using atlas frame for ${imagePath} (found using filename ${filename}) ${atlasId ? `from atlas '${atlasId}'` : ""}`); } return frameTexture; } if (frameTexture) { this.log(`Using atlas frame for ${imagePath} ${atlasId ? `from atlas '${atlasId}'` : ""}`); return frameTexture; } } if (this.imageTextures.has(imagePath)) { return this.imageTextures.get(imagePath); } const status = this.imageStatus.get(imagePath); if (status === "loading" /* Loading */) { this.log(`Image ${imagePath} is already loading, waiting...`); return new Promise((resolve) => { const checkInterval = setInterval(() => { const currentStatus = this.imageStatus.get(imagePath); if (currentStatus === "loaded" /* Loaded */) { clearInterval(checkInterval); resolve(this.imageTextures.get(imagePath) || null); } else if (currentStatus === "failed" /* Failed */) { clearInterval(checkInterval); resolve(null); } }, 100); }); } try { this.imageStatus.set(imagePath, "loading" /* Loading */); let texture; if (Assets.cache.has(imagePath)) { texture = Assets.cache.get(imagePath); } else { this.log(`Loading individual texture from ${imagePath}`); texture = await Assets.load(imagePath); } this.imageTextures.set(imagePath, texture); this.imageStatus.set(imagePath, "loaded" /* Loaded */); if (this.resourceManager) { this.resourceManager.trackTexture(imagePath, texture); } return texture; } catch (error) { this.log(`Error loading individual texture ${imagePath}: ${error}`, "error"); this.imageStatus.set(imagePath, "failed" /* Failed */); return null; } } /** * Preload a set of images, preferably from atlas(es) * * @param imagePaths - Array of image paths to preload * @param atlasIds - Optional array of atlas IDs to search for frames * @param progressCallback - Optional callback for loading progress * @returns Promise resolving when all images are loaded */ async preloadImages(imagePaths, atlasIds, progressCallback) { if (!imagePaths.length) { return; } let loadedCount = 0; const totalCount = imagePaths.length; const updateProgress = () => { loadedCount++; if (progressCallback) { progressCallback(loadedCount / totalCount); } }; const loadPromises = imagePaths.map(async (imagePath) => { try { if (atlasIds && atlasIds.length > 0) { for (const atlasId of atlasIds) { const texture = await this.getTexture(imagePath, atlasId); if (texture) { updateProgress(); return; } } await this.getTexture(imagePath); } else { await this.getTexture(imagePath); } updateProgress(); } catch (error) { this.log(`Error preloading image ${imagePath}: ${error}`, "warn"); updateProgress(); } }); await Promise.all(loadPromises); } /** * Unload an atlas and its resources * * @param atlasId - ID of the atlas to unload */ unloadAtlas(atlasId) { if (!this.atlases.has(atlasId)) { this.log(`Atlas '${atlasId}' not found, cannot unload`, "warn"); return; } try { const atlasTexture = this.atlasTextures.get(atlasId); if (this.options.cacheFrameTextures) { const prefix = `${atlasId}:`; for (const [key] of this.frameTextures.entries()) { if (key.startsWith(prefix)) { this.frameTextures.delete(key); } } } this.atlases.delete(atlasId); if (atlasTexture) { this.atlasTextures.delete(atlasId); } this.atlasStatus.delete(atlasId); this.log(`Atlas '${atlasId}' unloaded`); } catch (error) { this.log(`Error unloading atlas '${atlasId}': ${error}`, "error"); } } /** * Unload an individual image texture * * @param imagePath - Path to the image */ unloadTexture(imagePath) { if (!this.imageTextures.has(imagePath)) { return; } try { this.imageTextures.delete(imagePath); this.imageStatus.delete(imagePath); this.log(`Texture ${imagePath} unloaded`); } catch (error) { this.log(`Error unloading texture ${imagePath}: ${error}`, "error"); } } /** * Clean up all resources */ dispose() { try { this.atlases.clear(); this.atlasTextures.clear(); this.frameTextures.clear(); this.imageTextures.clear(); this.atlasStatus.clear(); this.imageStatus.clear(); this.log("AtlasManager disposed"); } catch (error) { this.log(`Error disposing AtlasManager: ${error}`, "error"); } } /** * Log a message with the appropriate level * * @param message - Message to log * @param level - Log level */ log(message, level = "log") { if (!this.options.debug && level === "log") { return; } const prefix = "[AtlasManager]"; switch (level) { case "warn": console.warn(`${prefix} ${message}`); break; case "error": console.error(`${prefix} ${message}`); break; default: console.log(`${prefix} ${message}`); break; } } } export { AtlasManager }; //# sourceMappingURL=AtlasManager.js.map