From 3a9d32dfeebd2027f985885abcc0de8f9a63187f Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Wed, 2 Dec 2020 23:19:41 -0800 Subject: [PATCH] Sketch out animating .ani cursors --- packages/webamp/js/components/Skin.tsx | 45 +++++++++++++++++++------- packages/webamp/js/skinParserUtils.ts | 8 ++--- packages/webamp/js/types.ts | 13 +++++++- 3 files changed, 50 insertions(+), 16 deletions(-) diff --git a/packages/webamp/js/components/Skin.tsx b/packages/webamp/js/components/Skin.tsx index 0b2a1b54..2c81c394 100644 --- a/packages/webamp/js/components/Skin.tsx +++ b/packages/webamp/js/components/Skin.tsx @@ -5,6 +5,7 @@ import { useTypedSelector } from "../hooks"; import * as Selectors from "../selectors"; import { SkinImages } from "../types"; import { createSelector } from "reselect"; +import * as Utils from "../utils"; import Css from "./Css"; import ClipPaths from "./ClipPaths"; @@ -67,19 +68,41 @@ const getCssRules = createSelector( ); }); } + Object.keys(cursorSelectors).forEach((cursorName) => { - const cursorUrl = skinCursors[cursorName]; - if (cursorUrl) { + const cursor = skinCursors[cursorName]; + if (cursor) { cursorSelectors[cursorName].forEach((selector) => { - cssRules.push( - `${ - // TODO: Fix this hack - // Maybe our CSS name spacing should be based on some other class/id - // than the one we use for defining the main div. - // That way it could be shared by both the player and the context menu. - selector.startsWith("#webamp-context-menu") ? "" : CSS_PREFIX - } ${selector} {cursor: url(${cursorUrl}), auto}` - ); + const normalizedSelector = `${ + // TODO: Fix this hack + // Maybe our CSS name spacing should be based on some other class/id + // than the one we use for defining the main div. + // That way it could be shared by both the player and the context menu. + selector.startsWith("#webamp-context-menu") ? "" : CSS_PREFIX + } ${selector}`; + + switch (cursor.type) { + case "cur": + cssRules.push( + `${normalizedSelector} {cursor: url(${cursor.url}), auto}` + ); + break; + case "ani": { + const animationName = `ani-cursor-${Utils.uniqueId()}`; + const frames = cursor.urls.map((url, i) => { + const percent = (i / (cursor.urls.length - 1)) * 100; + return `${percent}% { cursor: url(${url}), auto; }`; + }); + const durationMs = cursor.iDispRate * 16 * frames.length; + const framesCss = frames.join("\n"); + const keyframes = `@keyframes ${animationName} { ${framesCss} }`; + cssRules.push(keyframes); + cssRules.push( + `${normalizedSelector} { animation: ${animationName} ${durationMs}ms infinite; }` + ); + break; + } + } }); } }); diff --git a/packages/webamp/js/skinParserUtils.ts b/packages/webamp/js/skinParserUtils.ts index ea0ac452..2efaceb8 100644 --- a/packages/webamp/js/skinParserUtils.ts +++ b/packages/webamp/js/skinParserUtils.ts @@ -1,5 +1,5 @@ import JSZip from "jszip"; -import { PlaylistStyle, SkinGenExColors } from "./types"; +import { PlaylistStyle, SkinGenExColors, CursorImage } from "./types"; import SKIN_SPRITES, { Sprite } from "./skinSprites"; import { DEFAULT_SKIN } from "./constants"; import * as Utils from "./utils"; @@ -129,7 +129,7 @@ const ANI_MAGIC_BASE_64 = "UklGR"; export async function getCursorFromFilename( zip: JSZip, fileName: string -): Promise { +): Promise { const file = await getFileFromZip(zip, fileName, "CUR", "base64"); if (file == null) { return null; @@ -141,10 +141,10 @@ export async function getCursorFromFilename( throw new Error("We just read this file, how can it be null??"); } const ani = parseAni(fileArr.contents as Uint8Array); - return ani.urls[0]; + return { type: "ani", urls: ani.urls, iDispRate: ani.iDispRate }; } - return `data:image/x-win-bitmap;base64,${contents}`; + return { type: "cur", url: `data:image/x-win-bitmap;base64,${contents}` }; } export async function getPlaylistStyle(zip: JSZip): Promise { diff --git a/packages/webamp/js/types.ts b/packages/webamp/js/types.ts index a092f423..42225e0e 100644 --- a/packages/webamp/js/types.ts +++ b/packages/webamp/js/types.ts @@ -75,9 +75,20 @@ export type Band = export type Slider = Band | "preamp"; +export type CursorImage = + | { + type: "cur"; + url: string; + } + | { + type: "ani"; + iDispRate: number; + urls: string[]; + }; + // TODO: Use a type to ensure these keys mirror the CURSORS constant in // skinParser.js -export type Cursors = { [cursor: string]: string }; +export type Cursors = { [cursor: string]: CursorImage }; export type GenLetterWidths = { [letter: string]: number };