Initial commit: NexusOS project + desktop theme baseline
Captures the known-good state after theme consolidation and consistency reconciliation: - NexusOS GTK/xfwm4/icon theme assets (assets/themes, management/Mint-Y-Nexus) - Tightened right-click menus, visible separators, no menu icons - assets/themes/install-theme.sh: idempotent restore of all wiring (symlinks, xfconf xsettings+xfwm4, GTK 3/4 settings.ini) - .gitignore excludes venv/ollama/models/runtime/db Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,254 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
NexusOS folder icon builder.
|
||||
|
||||
Emits one SVG per variant into places/, then rasterizes to
|
||||
~/.icons/NexusOS/<size>/places/<name>.png at 16/22/24/32/48/64/128.
|
||||
|
||||
Run: python3 build.py # builds SVGs + renders all PNGs
|
||||
python3 build.py --svg-only # just rewrite SVGs
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
import base64
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
PLACES = ROOT / "places"
|
||||
ICONS_OUT = Path.home() / ".icons" / "NexusOS"
|
||||
SIZES = [16, 22, 24, 32, 48, 64, 128]
|
||||
|
||||
# N logo source. Inkscape's headless renderer doesn't follow external href
|
||||
# references, so we inline it as a base64 data URI.
|
||||
LOGO_PATH = Path.home() / "nexus-core" / "assets" / "n-small.png"
|
||||
LOGO_DATA_URI = (
|
||||
"data:image/png;base64,"
|
||||
+ base64.b64encode(LOGO_PATH.read_bytes()).decode("ascii")
|
||||
)
|
||||
|
||||
# ── master folder geometry ────────────────────────────────────────────
|
||||
# viewBox 0 0 128 128. Badge area: roughly (32,52)-(96,108).
|
||||
MASTER = """\
|
||||
<!-- Back of folder: tab + body silhouette in green -->
|
||||
<path d="M 14 24 Q 14 20 18 20 L 50 20 Q 53 20 55 22 L 62 30
|
||||
L 110 30 Q 114 30 114 34 L 114 110 Q 114 114 110 114
|
||||
L 18 114 Q 14 114 14 110 Z" fill="#8cc63f"/>
|
||||
<!-- White inner lip stripe -->
|
||||
<rect x="14" y="36" width="100" height="3" fill="#f2f2f2"/>
|
||||
<!-- Front face: deep purple body -->
|
||||
<path d="M 14 39 L 114 39 L 114 110 Q 114 114 110 114
|
||||
L 18 114 Q 14 114 14 110 Z" fill="#5e0066"/>
|
||||
<!-- Top-edge highlight on the front face -->
|
||||
<rect x="14" y="39" width="100" height="2" fill="#88008f"/>
|
||||
"""
|
||||
|
||||
# Open-folder geometry used by folder-open and folder-drag-accept.
|
||||
# The front face is angled forward, exposing the back as a triangle on top.
|
||||
OPEN_MASTER = """\
|
||||
<!-- Folder back, same as closed -->
|
||||
<path d="M 14 24 Q 14 20 18 20 L 50 20 Q 53 20 55 22 L 62 30
|
||||
L 110 30 Q 114 30 114 34 L 114 110 Q 114 114 110 114
|
||||
L 18 114 Q 14 114 14 110 Z" fill="#8cc63f"/>
|
||||
<!-- White inner lip stripe -->
|
||||
<rect x="14" y="36" width="100" height="3" fill="#f2f2f2"/>
|
||||
<!-- Front face, tilted forward — wider at bottom, narrower at top -->
|
||||
<path d="M 22 44 L 106 44 L 118 110 Q 118 114 114 114
|
||||
L 14 114 Q 10 114 10 110 Z" fill="#5e0066"/>
|
||||
<!-- Top-edge highlight on tilted front face -->
|
||||
<path d="M 22 44 L 106 44 L 106.5 46 L 21.5 46 Z" fill="#88008f"/>
|
||||
"""
|
||||
|
||||
# ── badge fragments ───────────────────────────────────────────────────
|
||||
# All badges drawn in white (#f2f2f2) and centered around (64, 78).
|
||||
# Keep glyphs in roughly a 40-50px box for legibility at 22-32 sizes.
|
||||
|
||||
BADGES = {
|
||||
"folder": "", # the base, no badge
|
||||
|
||||
"folder-documents": """\
|
||||
<!-- Document with folded corner -->
|
||||
<path d="M 48 58 L 72 58 L 80 66 L 80 98 L 48 98 Z"
|
||||
fill="#f2f2f2"/>
|
||||
<path d="M 72 58 L 72 66 L 80 66 Z" fill="#cccccc"/>
|
||||
<rect x="52" y="72" width="24" height="2" fill="#5e0066"/>
|
||||
<rect x="52" y="78" width="24" height="2" fill="#5e0066"/>
|
||||
<rect x="52" y="84" width="20" height="2" fill="#5e0066"/>
|
||||
<rect x="52" y="90" width="24" height="2" fill="#5e0066"/>
|
||||
""",
|
||||
|
||||
"folder-download": """\
|
||||
<!-- Down arrow over a tray -->
|
||||
<rect x="60" y="56" width="8" height="22" fill="#f2f2f2"/>
|
||||
<path d="M 50 76 L 78 76 L 64 92 Z" fill="#f2f2f2"/>
|
||||
<rect x="48" y="96" width="32" height="4" rx="1" fill="#f2f2f2"/>
|
||||
""",
|
||||
|
||||
"folder-music": """\
|
||||
<!-- Eighth note -->
|
||||
<rect x="68" y="58" width="4" height="32" fill="#f2f2f2"/>
|
||||
<path d="M 68 58 L 84 62 L 84 70 L 72 66 Z" fill="#f2f2f2"/>
|
||||
<ellipse cx="62" cy="92" rx="9" ry="6" fill="#f2f2f2"/>
|
||||
""",
|
||||
|
||||
"folder-pictures": """\
|
||||
<!-- Landscape: sun + mountains in a frame -->
|
||||
<rect x="44" y="58" width="40" height="40" rx="2" fill="#f2f2f2"/>
|
||||
<circle cx="74" cy="68" r="4" fill="#8cc63f"/>
|
||||
<path d="M 46 96 L 60 78 L 70 88 L 78 80 L 82 96 Z" fill="#5e0066"/>
|
||||
""",
|
||||
|
||||
"folder-videos": """\
|
||||
<!-- Filmstrip with play triangle -->
|
||||
<rect x="44" y="60" width="40" height="36" rx="2" fill="#f2f2f2"/>
|
||||
<rect x="44" y="60" width="40" height="4" fill="#5e0066"/>
|
||||
<rect x="44" y="92" width="40" height="4" fill="#5e0066"/>
|
||||
<rect x="46" y="60" width="4" height="4" fill="#f2f2f2"/>
|
||||
<rect x="56" y="60" width="4" height="4" fill="#f2f2f2"/>
|
||||
<rect x="66" y="60" width="4" height="4" fill="#f2f2f2"/>
|
||||
<rect x="76" y="60" width="4" height="4" fill="#f2f2f2"/>
|
||||
<rect x="46" y="92" width="4" height="4" fill="#f2f2f2"/>
|
||||
<rect x="56" y="92" width="4" height="4" fill="#f2f2f2"/>
|
||||
<rect x="66" y="92" width="4" height="4" fill="#f2f2f2"/>
|
||||
<rect x="76" y="92" width="4" height="4" fill="#f2f2f2"/>
|
||||
<path d="M 58 70 L 74 78 L 58 86 Z" fill="#8cc63f"/>
|
||||
""",
|
||||
|
||||
"folder-templates": """\
|
||||
<!-- Pencil over a page -->
|
||||
<path d="M 50 60 L 70 60 L 78 68 L 78 96 L 50 96 Z" fill="#f2f2f2"/>
|
||||
<path d="M 70 60 L 70 68 L 78 68 Z" fill="#cccccc"/>
|
||||
<!-- Pencil at 45deg -->
|
||||
<path d="M 60 88 L 80 68 L 84 72 L 64 92 Z" fill="#8cc63f"/>
|
||||
<path d="M 60 88 L 64 92 L 58 94 Z" fill="#5e0066"/>
|
||||
""",
|
||||
|
||||
"folder-publicshare": """\
|
||||
<!-- Three small people silhouettes -->
|
||||
<circle cx="52" cy="68" r="6" fill="#f2f2f2"/>
|
||||
<path d="M 42 92 Q 42 78 52 78 Q 62 78 62 92 Z" fill="#f2f2f2"/>
|
||||
<circle cx="76" cy="68" r="6" fill="#f2f2f2"/>
|
||||
<path d="M 66 92 Q 66 78 76 78 Q 86 78 86 92 Z" fill="#f2f2f2"/>
|
||||
<circle cx="64" cy="60" r="5" fill="#f2f2f2"/>
|
||||
<path d="M 56 76 Q 56 66 64 66 Q 72 66 72 76 Z" fill="#f2f2f2"/>
|
||||
""",
|
||||
|
||||
"folder-home": """\
|
||||
<!-- House silhouette -->
|
||||
<path d="M 64 56 L 88 76 L 84 76 L 84 96 L 70 96 L 70 84
|
||||
L 58 84 L 58 96 L 44 96 L 44 76 L 40 76 Z"
|
||||
fill="#f2f2f2"/>
|
||||
""",
|
||||
|
||||
"folder-recent": """\
|
||||
<!-- Clock face -->
|
||||
<circle cx="64" cy="80" r="20" fill="#f2f2f2"/>
|
||||
<circle cx="64" cy="80" r="20" fill="none" stroke="#5e0066" stroke-width="2"/>
|
||||
<rect x="63" y="66" width="2" height="16" fill="#5e0066"/>
|
||||
<rect x="63" y="79" width="14" height="2" fill="#5e0066"/>
|
||||
""",
|
||||
|
||||
"folder-remote": """\
|
||||
<!-- Globe / network -->
|
||||
<circle cx="64" cy="80" r="20" fill="#f2f2f2"/>
|
||||
<ellipse cx="64" cy="80" rx="20" ry="8" fill="none" stroke="#5e0066" stroke-width="1.5"/>
|
||||
<ellipse cx="64" cy="80" rx="8" ry="20" fill="none" stroke="#5e0066" stroke-width="1.5"/>
|
||||
<line x1="44" y1="80" x2="84" y2="80" stroke="#5e0066" stroke-width="1.5"/>
|
||||
<line x1="64" y1="60" x2="64" y2="100" stroke="#5e0066" stroke-width="1.5"/>
|
||||
""",
|
||||
|
||||
"folder-saved-search": """\
|
||||
<!-- Magnifying glass -->
|
||||
<circle cx="58" cy="74" r="12" fill="none" stroke="#f2f2f2" stroke-width="4"/>
|
||||
<line x1="68" y1="84" x2="82" y2="98" stroke="#f2f2f2" stroke-width="5" stroke-linecap="round"/>
|
||||
""",
|
||||
|
||||
"folder-drag-accept": "USE_OPEN_MASTER", # uses OPEN_MASTER, no extra badge
|
||||
"folder-open": "USE_OPEN_MASTER",
|
||||
|
||||
"folder-nexus-core": f"""\
|
||||
<!-- N logo (base64 of assets/n-small.png), centered on front face. -->
|
||||
<!-- Source is 598x341 (aspect 1.754:1). -->
|
||||
<image xlink:href="{LOGO_DATA_URI}" x="28" y="58" width="72" height="41"
|
||||
preserveAspectRatio="xMidYMid meet"/>
|
||||
""",
|
||||
}
|
||||
|
||||
|
||||
def svg_for(name: str, badge: str) -> str:
|
||||
if badge == "USE_OPEN_MASTER":
|
||||
body = OPEN_MASTER
|
||||
extra = ""
|
||||
else:
|
||||
body = MASTER
|
||||
extra = badge
|
||||
return f"""<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128" viewBox="0 0 128 128">
|
||||
{body}{extra}</svg>
|
||||
"""
|
||||
|
||||
|
||||
def write_svgs() -> list[str]:
|
||||
PLACES.mkdir(parents=True, exist_ok=True)
|
||||
written = []
|
||||
for name, badge in BADGES.items():
|
||||
path = PLACES / f"{name}.svg"
|
||||
path.write_text(svg_for(name, badge))
|
||||
written.append(name)
|
||||
return written
|
||||
|
||||
|
||||
def render_pngs(names: list[str]) -> None:
|
||||
for name in names:
|
||||
svg = PLACES / f"{name}.svg"
|
||||
for size in SIZES:
|
||||
out_dir = ICONS_OUT / f"{size}x{size}" / "places"
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
out = out_dir / f"{name}.png"
|
||||
subprocess.run(
|
||||
[
|
||||
"inkscape",
|
||||
str(svg),
|
||||
"--export-type=png",
|
||||
f"--export-filename={out}",
|
||||
f"--export-width={size}",
|
||||
f"--export-height={size}",
|
||||
],
|
||||
check=True,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
print(f" rendered {name} @ {size}")
|
||||
|
||||
|
||||
def backup_existing_nexus_core() -> None:
|
||||
"""Move the existing chunky 3D folder-nexus-core PNGs aside before overwriting."""
|
||||
backup = ROOT / "_backup" / "folder-nexus-core-original"
|
||||
backup.mkdir(parents=True, exist_ok=True)
|
||||
for size in SIZES:
|
||||
src = ICONS_OUT / f"{size}x{size}" / "places" / "folder-nexus-core.png"
|
||||
if src.exists():
|
||||
shutil.copy2(src, backup / f"{size}.png")
|
||||
print(f" backed up original folder-nexus-core to {backup}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
svg_only = "--svg-only" in sys.argv
|
||||
names = write_svgs()
|
||||
print(f"Wrote {len(names)} SVGs into {PLACES}")
|
||||
if svg_only:
|
||||
return
|
||||
backup_existing_nexus_core()
|
||||
render_pngs(names)
|
||||
# Refresh the GTK icon cache so file managers pick up the new icons.
|
||||
subprocess.run(
|
||||
["gtk-update-icon-cache", "-f", str(ICONS_OUT)],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
print(f"\nDone. Set theme to NexusOS in your DE to see the icons.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user