// Allgemeine Symbole (Ordner, Pfeile, Einstellungen ...) fuer das Icon-Pack, in Einheitskoordinaten [-1, 1]. const TAU = Math.PI * 2; export const EXTRA_GLYPHS = [ 'folder', 'file', 'home', 'gear', 'plus', 'minus', 'close', 'check', 'star', 'search', 'power', 'mic', 'headphones', 'lock', 'bell', 'clock', 'calendar', 'mail', 'globe', 'download', 'upload', 'trash', 'eye', 'stop', 'refresh', 'wifi', 'grid', 'menu', 'more', 'info', 'warning', 'monitor', 'sun', 'moon', 'arrow-up', 'arrow-down', 'arrow-left', 'arrow-right', 'chevron-up', 'chevron-down', 'chevron-left', 'chevron-right', ]; const ARROW_ANGLES = { 'arrow-right': 0, 'arrow-down': 90, 'arrow-left': 180, 'arrow-up': 270 }; const CHEVRON_ANGLES = { 'chevron-right': 0, 'chevron-down': 90, 'chevron-left': 180, 'chevron-up': 270 }; function starPoints() { const points = []; for (let i = 0; i < 10; i += 1) { const angle = -Math.PI / 2 + (i * Math.PI) / 5; const radius = i % 2 === 0 ? 0.98 : 0.42; points.push([Math.cos(angle) * radius, Math.sin(angle) * radius + 0.05]); } return points; } const STAR = starPoints(); /** * @param {import('./raster.mjs').Canvas} canvas * @param {string} name - Eintrag aus EXTRA_GLYPHS * @param {number} cx * @param {number} cy * @param {number} r - halbe Symbolgroesse in Pixeln * @param {string|Function} color * @param {string|Function} background - Farbe fuer "ausgeschnittene" Teile */ export function drawExtra(canvas, name, cx, cy, r, color, background) { const p = (x, y) => [cx + x * r, cy + y * r]; const rot = (x, y, degrees) => { const a = (degrees * Math.PI) / 180; return [x * Math.cos(a) - y * Math.sin(a), x * Math.sin(a) + y * Math.cos(a)]; }; const stroke = (x1, y1, x2, y2, w = 0.2, paint = color, degrees = 0) => { const [ax, ay] = p(...rot(x1, y1, degrees)); const [bx, by] = p(...rot(x2, y2, degrees)); canvas.line(ax, ay, bx, by, w * r, paint); }; const path = (points, w = 0.2, degrees = 0) => { points.slice(1).forEach((pt, i) => stroke(...points[i], ...pt, w, color, degrees)); }; const poly = (points, paint = color) => canvas.polygon(points.map(([x, y]) => p(x, y)), paint); const disc = (x, y, radius, paint = color) => canvas.circle(...p(x, y), radius * r, paint); const ring = (x, y, outer, inner, from = 0, to = TAU) => canvas.ring(...p(x, y), outer * r, inner * r, color, from, to); const box = (x, y, w, h, radius, paint = color) => canvas.roundRect(...p(x, y), w * r, h * r, radius * r, paint); // Ellipse; thickness = Randstaerke relativ zum Radius, null = gefuellt. const ellipse = (rx, ry, thickness) => { const [ex, ey] = p(0, 0); canvas.fill((px, py) => { const d = Math.hypot((px - ex) / (rx * r), (py - ey) / (ry * r)); return d <= 1 && (thickness === null || d >= 1 - thickness); }, color, [ex - rx * r, ey - ry * r, ex + rx * r, ey + ry * r]); }; if (name in ARROW_ANGLES) { const a = ARROW_ANGLES[name]; stroke(-0.8, 0, 0.8, 0, 0.2, color, a); stroke(0.8, 0, 0.2, -0.6, 0.2, color, a); stroke(0.8, 0, 0.2, 0.6, 0.2, color, a); return; } if (name in CHEVRON_ANGLES) { path([[-0.3, -0.75], [0.35, 0], [-0.3, 0.75]], 0.24, CHEVRON_ANGLES[name]); return; } switch (name) { case 'folder': poly([[-0.92, -0.5], [-0.92, -0.78], [-0.15, -0.78], [0.1, -0.5]]); box(-0.92, -0.5, 1.84, 1.3, 0.12); break; case 'file': poly([[-0.62, -0.92], [0.18, -0.92], [0.68, -0.42], [0.68, 0.92], [-0.62, 0.92]]); poly([[0.18, -0.92], [0.18, -0.42], [0.68, -0.42]], background); stroke(-0.3, 0.05, 0.36, 0.05, 0.14, background); stroke(-0.3, 0.45, 0.36, 0.45, 0.14, background); break; case 'home': poly([[-1, 0], [0, -0.9], [1, 0]]); box(-0.62, -0.05, 1.24, 0.9, 0.06); box(-0.16, 0.25, 0.32, 0.6, 0.04, background); break; case 'gear': ring(0, 0, 0.66, 0.3); for (let i = 0; i < 8; i += 1) { const a = (i * 45 * Math.PI) / 180; stroke(Math.cos(a) * 0.6, Math.sin(a) * 0.6, Math.cos(a) * 0.9, Math.sin(a) * 0.9, 0.3); } break; case 'plus': stroke(-0.8, 0, 0.8, 0, 0.24); stroke(0, -0.8, 0, 0.8, 0.24); break; case 'minus': stroke(-0.8, 0, 0.8, 0, 0.24); break; case 'close': stroke(-0.7, -0.7, 0.7, 0.7, 0.24); stroke(-0.7, 0.7, 0.7, -0.7, 0.24); break; case 'check': path([[-0.8, 0.05], [-0.25, 0.6], [0.85, -0.55]], 0.26); break; case 'star': poly(STAR); break; case 'search': ring(-0.2, -0.2, 0.68, 0.44); stroke(0.3, 0.3, 0.82, 0.82, 0.28); break; case 'power': ring(0, 0.08, 0.8, 0.58, (3 * Math.PI) / 2 + 0.65, TAU); ring(0, 0.08, 0.8, 0.58, 0, (3 * Math.PI) / 2 - 0.65); stroke(0, -0.9, 0, -0.1, 0.24); break; case 'mic': box(-0.28, -0.9, 0.56, 1.1, 0.28); ring(0, -0.05, 0.6, 0.42, 0, Math.PI); stroke(0, 0.55, 0, 0.9, 0.16); stroke(-0.35, 0.9, 0.35, 0.9, 0.16); break; case 'headphones': ring(0, 0.05, 0.85, 0.62, Math.PI, TAU); box(-0.95, 0.05, 0.4, 0.7, 0.12); box(0.55, 0.05, 0.4, 0.7, 0.12); break; case 'lock': ring(0, -0.15, 0.45, 0.25, Math.PI, TAU); stroke(-0.35, -0.15, -0.35, 0.05, 0.2); stroke(0.35, -0.15, 0.35, 0.05, 0.2); box(-0.65, 0, 1.3, 0.9, 0.12); disc(0, 0.4, 0.13, background); break; case 'bell': disc(0, -0.15, 0.55); poly([[-0.55, -0.15], [0.55, -0.15], [0.82, 0.5], [-0.82, 0.5]]); disc(0, 0.72, 0.16); stroke(0, -0.85, 0, -0.65, 0.16); break; case 'clock': ring(0, 0, 0.92, 0.7); stroke(0, 0, 0, -0.5, 0.15); stroke(0, 0, 0.36, 0.22, 0.15); break; case 'calendar': box(-0.88, -0.68, 1.76, 1.58, 0.12); box(-0.72, -0.22, 1.44, 1.0, 0.05, background); stroke(-0.4, -0.92, -0.4, -0.5, 0.18); stroke(0.4, -0.92, 0.4, -0.5, 0.18); break; case 'mail': box(-0.92, -0.62, 1.84, 1.24, 0.12); stroke(-0.8, -0.5, 0, 0.12, 0.15, background); stroke(0, 0.12, 0.8, -0.5, 0.15, background); break; case 'globe': ring(0, 0, 0.92, 0.75); ellipse(0.42, 0.92, 0.18); stroke(-0.92, 0, 0.92, 0, 0.13); break; case 'download': stroke(0, -0.9, 0, 0.3, 0.22); path([[-0.5, -0.2], [0, 0.4], [0.5, -0.2]], 0.22); path([[-0.85, 0.35], [-0.85, 0.85], [0.85, 0.85], [0.85, 0.35]], 0.2); break; case 'upload': stroke(0, 0.3, 0, -0.9, 0.22); path([[-0.5, -0.4], [0, -0.95], [0.5, -0.4]], 0.22); path([[-0.85, 0.35], [-0.85, 0.85], [0.85, 0.85], [0.85, 0.35]], 0.2); break; case 'trash': box(-0.6, -0.3, 1.2, 1.2, 0.1); stroke(-0.85, -0.5, 0.85, -0.5, 0.2); stroke(-0.25, -0.75, 0.25, -0.75, 0.16); stroke(-0.2, -0.05, -0.2, 0.65, 0.13, background); stroke(0.2, -0.05, 0.2, 0.65, 0.13, background); break; case 'eye': ellipse(0.98, 0.58, 0.22); disc(0, 0, 0.3); break; case 'stop': box(-0.72, -0.72, 1.44, 1.44, 0.16); break; case 'refresh': { const end = 5.5; ring(0, 0, 0.85, 0.6, 0.7, end); const bx = Math.cos(end) * 0.725; const by = Math.sin(end) * 0.725; poly([[bx - Math.sin(end) * 0.55, by + Math.cos(end) * 0.55], [bx + Math.cos(end) * 0.5, by + Math.sin(end) * 0.5], [bx - Math.cos(end) * 0.5, by - Math.sin(end) * 0.5]]); break; } case 'wifi': for (const radius of [0.45, 0.8, 1.15]) ring(0, 0.6, radius + 0.09, radius - 0.09, 1.25 * Math.PI, 1.75 * Math.PI); disc(0, 0.6, 0.13); break; case 'grid': for (const x of [-0.85, 0.08]) for (const y of [-0.85, 0.08]) box(x, y, 0.77, 0.77, 0.14); break; case 'menu': stroke(-0.8, -0.5, 0.8, -0.5, 0.22); stroke(-0.8, 0, 0.8, 0, 0.22); stroke(-0.8, 0.5, 0.8, 0.5, 0.22); break; case 'more': for (const x of [-0.6, 0, 0.6]) disc(x, 0, 0.17); break; case 'info': ring(0, 0, 0.92, 0.72); disc(0, -0.42, 0.11); stroke(0, -0.12, 0, 0.5, 0.18); break; case 'warning': poly([[0, -0.9], [0.98, 0.78], [-0.98, 0.78]]); stroke(0, -0.3, 0, 0.25, 0.16, background); disc(0, 0.52, 0.09, background); break; case 'monitor': box(-0.92, -0.72, 1.84, 1.2, 0.12); box(-0.78, -0.58, 1.56, 0.92, 0.05, background); stroke(0, 0.5, 0, 0.8, 0.2); stroke(-0.45, 0.85, 0.45, 0.85, 0.2); break; case 'sun': disc(0, 0, 0.42); for (let i = 0; i < 8; i += 1) { const a = (i * 45 * Math.PI) / 180; stroke(Math.cos(a) * 0.68, Math.sin(a) * 0.68, Math.cos(a) * 0.95, Math.sin(a) * 0.95, 0.17); } break; case 'moon': disc(0, 0, 0.8); disc(0.38, -0.3, 0.68, background); break; default: throw new Error(`Unbekanntes Symbol: ${name}`); } }