add new style and global search
This commit is contained in:
@@ -0,0 +1,326 @@
|
||||
import { Buffer } from "node:buffer";
|
||||
import { mkdirSync, writeFileSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { deflateSync } from "node:zlib";
|
||||
|
||||
const root = dirname(dirname(fileURLToPath(import.meta.url)));
|
||||
const iconsDir = join(root, "src-tauri", "icons");
|
||||
|
||||
const icoSizes = [16, 24, 32, 48, 64, 128, 256];
|
||||
|
||||
function clamp(value, min = 0, max = 1) {
|
||||
return Math.min(max, Math.max(min, value));
|
||||
}
|
||||
|
||||
function smoothstep(edge0, edge1, value) {
|
||||
const t = clamp((value - edge0) / (edge1 - edge0));
|
||||
return t * t * (3 - 2 * t);
|
||||
}
|
||||
|
||||
function hex(value) {
|
||||
const clean = value.replace("#", "");
|
||||
return {
|
||||
r: Number.parseInt(clean.slice(0, 2), 16),
|
||||
g: Number.parseInt(clean.slice(2, 4), 16),
|
||||
b: Number.parseInt(clean.slice(4, 6), 16),
|
||||
};
|
||||
}
|
||||
|
||||
function mix(a, b, t) {
|
||||
const u = clamp(t);
|
||||
return {
|
||||
r: a.r + (b.r - a.r) * u,
|
||||
g: a.g + (b.g - a.g) * u,
|
||||
b: a.b + (b.b - a.b) * u,
|
||||
};
|
||||
}
|
||||
|
||||
function addGlow(color, glow, amount) {
|
||||
return {
|
||||
r: clamp(color.r + glow.r * amount, 0, 255),
|
||||
g: clamp(color.g + glow.g * amount, 0, 255),
|
||||
b: clamp(color.b + glow.b * amount, 0, 255),
|
||||
};
|
||||
}
|
||||
|
||||
function blend(base, layer) {
|
||||
const alpha = clamp(layer.a);
|
||||
const outA = alpha + base.a * (1 - alpha);
|
||||
if (outA <= 0) return { r: 0, g: 0, b: 0, a: 0 };
|
||||
|
||||
return {
|
||||
r: (layer.r * alpha + base.r * base.a * (1 - alpha)) / outA,
|
||||
g: (layer.g * alpha + base.g * base.a * (1 - alpha)) / outA,
|
||||
b: (layer.b * alpha + base.b * base.a * (1 - alpha)) / outA,
|
||||
a: outA,
|
||||
};
|
||||
}
|
||||
|
||||
function roundedRectDistance(x, y, cx, cy, hx, hy, radius) {
|
||||
const qx = Math.abs(x - cx) - hx + radius;
|
||||
const qy = Math.abs(y - cy) - hy + radius;
|
||||
const outsideX = Math.max(qx, 0);
|
||||
const outsideY = Math.max(qy, 0);
|
||||
return Math.hypot(outsideX, outsideY) + Math.min(Math.max(qx, qy), 0) - radius;
|
||||
}
|
||||
|
||||
function segmentDistance(px, py, ax, ay, bx, by) {
|
||||
const vx = bx - ax;
|
||||
const vy = by - ay;
|
||||
const wx = px - ax;
|
||||
const wy = py - ay;
|
||||
const lenSq = vx * vx + vy * vy;
|
||||
const t = lenSq === 0 ? 0 : clamp((wx * vx + wy * vy) / lenSq);
|
||||
return Math.hypot(px - (ax + vx * t), py - (ay + vy * t));
|
||||
}
|
||||
|
||||
function polygonContains(x, y, points) {
|
||||
let inside = false;
|
||||
for (let i = 0, j = points.length - 1; i < points.length; j = i++) {
|
||||
const xi = points[i][0];
|
||||
const yi = points[i][1];
|
||||
const xj = points[j][0];
|
||||
const yj = points[j][1];
|
||||
const intersects = yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;
|
||||
if (intersects) inside = !inside;
|
||||
}
|
||||
return inside;
|
||||
}
|
||||
|
||||
function polygonDistance(x, y, points) {
|
||||
let distance = Infinity;
|
||||
for (let i = 0; i < points.length; i += 1) {
|
||||
const a = points[i];
|
||||
const b = points[(i + 1) % points.length];
|
||||
distance = Math.min(distance, segmentDistance(x, y, a[0], a[1], b[0], b[1]));
|
||||
}
|
||||
return polygonContains(x, y, points) ? -distance : distance;
|
||||
}
|
||||
|
||||
function drawStroke(color, x, y, ax, ay, bx, by, radius, strokeColor, alpha = 1) {
|
||||
const d = segmentDistance(x, y, ax, ay, bx, by);
|
||||
const a = smoothstep(radius + 1.4, radius - 0.8, d) * alpha;
|
||||
if (a <= 0) return color;
|
||||
return blend(color, { ...strokeColor, a });
|
||||
}
|
||||
|
||||
function drawCircle(color, x, y, cx, cy, radius, circleColor, alpha = 1) {
|
||||
const d = Math.hypot(x - cx, y - cy);
|
||||
const a = smoothstep(radius + 1.4, radius - 0.8, d) * alpha;
|
||||
if (a <= 0) return color;
|
||||
return blend(color, { ...circleColor, a });
|
||||
}
|
||||
|
||||
function sampleScene(x, y) {
|
||||
const dark = hex("#10111f");
|
||||
const purple = hex("#bd34fe");
|
||||
const indigo = hex("#646cff");
|
||||
const cyan = hex("#41d1ff");
|
||||
const yellow = hex("#ffd343");
|
||||
const white = hex("#f7fbff");
|
||||
|
||||
let color = { r: 0, g: 0, b: 0, a: 0 };
|
||||
const badge = roundedRectDistance(x, y, 128, 128, 108, 108, 48);
|
||||
|
||||
const shadowA = Math.exp(-Math.max(badge, 0) / 12) * 0.22;
|
||||
if (badge > 0 && shadowA > 0.005) {
|
||||
color = blend(color, { r: 0, g: 0, b: 0, a: shadowA });
|
||||
}
|
||||
|
||||
const badgeA = smoothstep(1.6, -1.2, badge);
|
||||
if (badgeA > 0) {
|
||||
const diagonal = (x + y) / 512;
|
||||
let base = mix(dark, indigo, diagonal * 0.75);
|
||||
|
||||
const purpleGlow = Math.exp(-((x - 68) ** 2 + (y - 62) ** 2) / 6200);
|
||||
const cyanGlow = Math.exp(-((x - 184) ** 2 + (y - 72) ** 2) / 5600);
|
||||
const yellowGlow = Math.exp(-((x - 184) ** 2 + (y - 202) ** 2) / 8400);
|
||||
|
||||
base = addGlow(base, purple, purpleGlow * 0.58);
|
||||
base = addGlow(base, cyan, cyanGlow * 0.55);
|
||||
base = addGlow(base, yellow, yellowGlow * 0.22);
|
||||
|
||||
color = blend(color, { ...base, a: badgeA });
|
||||
|
||||
const rim = smoothstep(4.2, 0.4, Math.abs(badge));
|
||||
color = blend(color, { ...white, a: rim * 0.18 * badgeA });
|
||||
}
|
||||
|
||||
const bolt = [
|
||||
[144, 40],
|
||||
[198, 40],
|
||||
[164, 111],
|
||||
[207, 111],
|
||||
[113, 218],
|
||||
[143, 142],
|
||||
[103, 142],
|
||||
];
|
||||
const boltD = polygonDistance(x, y, bolt);
|
||||
const boltA = smoothstep(2.5, -1, boltD);
|
||||
if (boltA > 0) {
|
||||
color = blend(color, { ...yellow, a: boltA * 0.24 });
|
||||
}
|
||||
|
||||
const glow = hex("#41d1ff");
|
||||
const line = hex("#eef7ff");
|
||||
const nodeFill = hex("#131827");
|
||||
|
||||
for (const segment of [
|
||||
[82, 73, 82, 181],
|
||||
[82, 116, 166, 116],
|
||||
]) {
|
||||
color = drawStroke(color, x, y, ...segment, 12, glow, 0.24);
|
||||
color = drawStroke(color, x, y, ...segment, 5.2, line, 0.94);
|
||||
}
|
||||
|
||||
for (const [cx, cy, accent] of [
|
||||
[82, 73, cyan],
|
||||
[166, 116, purple],
|
||||
[82, 181, yellow],
|
||||
]) {
|
||||
color = drawCircle(color, x, y, cx, cy, 22, accent, 0.25);
|
||||
color = drawCircle(color, x, y, cx, cy, 14.5, white, 0.96);
|
||||
color = drawCircle(color, x, y, cx, cy, 8.4, nodeFill, 1);
|
||||
color = drawCircle(color, x, y, cx, cy, 4.2, accent, 0.95);
|
||||
}
|
||||
|
||||
const spark = [
|
||||
[186, 161],
|
||||
[195, 181],
|
||||
[215, 190],
|
||||
[195, 199],
|
||||
[186, 219],
|
||||
[177, 199],
|
||||
[157, 190],
|
||||
[177, 181],
|
||||
];
|
||||
const sparkD = polygonDistance(x, y, spark);
|
||||
const sparkA = smoothstep(1.8, -0.8, sparkD);
|
||||
if (sparkA > 0) {
|
||||
color = blend(color, { ...yellow, a: sparkA * 0.92 });
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
function renderPng(size) {
|
||||
const scale = 256 / size;
|
||||
const samples = size <= 32 ? 5 : 3;
|
||||
const data = Buffer.alloc(size * size * 4);
|
||||
|
||||
for (let y = 0; y < size; y += 1) {
|
||||
for (let x = 0; x < size; x += 1) {
|
||||
let r = 0;
|
||||
let g = 0;
|
||||
let b = 0;
|
||||
let a = 0;
|
||||
|
||||
for (let sy = 0; sy < samples; sy += 1) {
|
||||
for (let sx = 0; sx < samples; sx += 1) {
|
||||
const scene = sampleScene((x + (sx + 0.5) / samples) * scale, (y + (sy + 0.5) / samples) * scale);
|
||||
r += scene.r * scene.a;
|
||||
g += scene.g * scene.a;
|
||||
b += scene.b * scene.a;
|
||||
a += scene.a;
|
||||
}
|
||||
}
|
||||
|
||||
const total = samples * samples;
|
||||
const alpha = a / total;
|
||||
const offset = (y * size + x) * 4;
|
||||
data[offset] = alpha > 0 ? Math.round(r / a) : 0;
|
||||
data[offset + 1] = alpha > 0 ? Math.round(g / a) : 0;
|
||||
data[offset + 2] = alpha > 0 ? Math.round(b / a) : 0;
|
||||
data[offset + 3] = Math.round(alpha * 255);
|
||||
}
|
||||
}
|
||||
|
||||
return encodePng(size, size, data);
|
||||
}
|
||||
|
||||
const crcTable = new Uint32Array(256);
|
||||
for (let n = 0; n < 256; n += 1) {
|
||||
let c = n;
|
||||
for (let k = 0; k < 8; k += 1) {
|
||||
c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
||||
}
|
||||
crcTable[n] = c >>> 0;
|
||||
}
|
||||
|
||||
function crc32(buffer) {
|
||||
let crc = 0xffffffff;
|
||||
for (const byte of buffer) {
|
||||
crc = crcTable[(crc ^ byte) & 0xff] ^ (crc >>> 8);
|
||||
}
|
||||
return (crc ^ 0xffffffff) >>> 0;
|
||||
}
|
||||
|
||||
function chunk(type, payload) {
|
||||
const typeBytes = Buffer.from(type);
|
||||
const out = Buffer.alloc(12 + payload.length);
|
||||
out.writeUInt32BE(payload.length, 0);
|
||||
typeBytes.copy(out, 4);
|
||||
payload.copy(out, 8);
|
||||
out.writeUInt32BE(crc32(Buffer.concat([typeBytes, payload])), out.length - 4);
|
||||
return out;
|
||||
}
|
||||
|
||||
function encodePng(width, height, rgba) {
|
||||
const header = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
|
||||
const ihdr = Buffer.alloc(13);
|
||||
ihdr.writeUInt32BE(width, 0);
|
||||
ihdr.writeUInt32BE(height, 4);
|
||||
ihdr[8] = 8;
|
||||
ihdr[9] = 6;
|
||||
|
||||
const raw = Buffer.alloc((width * 4 + 1) * height);
|
||||
for (let y = 0; y < height; y += 1) {
|
||||
const rowStart = y * (width * 4 + 1);
|
||||
raw[rowStart] = 0;
|
||||
rgba.copy(raw, rowStart + 1, y * width * 4, (y + 1) * width * 4);
|
||||
}
|
||||
|
||||
return Buffer.concat([
|
||||
header,
|
||||
chunk("IHDR", ihdr),
|
||||
chunk("IDAT", deflateSync(raw, { level: 9 })),
|
||||
chunk("IEND", Buffer.alloc(0)),
|
||||
]);
|
||||
}
|
||||
|
||||
function makeIco(images) {
|
||||
const header = Buffer.alloc(6);
|
||||
header.writeUInt16LE(0, 0);
|
||||
header.writeUInt16LE(1, 2);
|
||||
header.writeUInt16LE(images.length, 4);
|
||||
|
||||
const entries = Buffer.alloc(images.length * 16);
|
||||
let offset = 6 + images.length * 16;
|
||||
|
||||
images.forEach((image, index) => {
|
||||
const entry = index * 16;
|
||||
entries[entry] = image.size >= 256 ? 0 : image.size;
|
||||
entries[entry + 1] = image.size >= 256 ? 0 : image.size;
|
||||
entries[entry + 2] = 0;
|
||||
entries[entry + 3] = 0;
|
||||
entries.writeUInt16LE(1, entry + 4);
|
||||
entries.writeUInt16LE(32, entry + 6);
|
||||
entries.writeUInt32LE(image.png.length, entry + 8);
|
||||
entries.writeUInt32LE(offset, entry + 12);
|
||||
offset += image.png.length;
|
||||
});
|
||||
|
||||
return Buffer.concat([header, entries, ...images.map((image) => image.png)]);
|
||||
}
|
||||
|
||||
mkdirSync(iconsDir, { recursive: true });
|
||||
|
||||
const iconPng = renderPng(512);
|
||||
const icoImages = icoSizes.map((size) => ({ size, png: renderPng(size) }));
|
||||
|
||||
writeFileSync(join(iconsDir, "icon.png"), iconPng);
|
||||
writeFileSync(join(iconsDir, "icon.ico"), makeIco(icoImages));
|
||||
|
||||
console.log(`Wrote ${join("src-tauri", "icons", "icon.png")} (${iconPng.length} bytes)`);
|
||||
console.log(`Wrote ${join("src-tauri", "icons", "icon.ico")} (${icoImages.length} sizes)`);
|
||||
Reference in New Issue
Block a user