Files
claude-timemachine f1cb9f4b86
CI / test (3.10) (push) Successful in 6s
CI / test (3.11) (push) Successful in 7s
CI / test (3.12) (push) Successful in 6s
CI / build-pyz (push) Successful in 4s
CI / release (push) Has been skipped
feat(ui): inline SVG icons across all three dialogs
New module cloud_sync/icons.py:
  - CLOUD_SVG, STORAGE_SVG: card glyphs (Material Icons, Apache-2.0)
  - WARNING_BADGE_SVG, PLUS_BADGE_SVG, SYNC_BADGE_SVG: header badges
    (hand-rolled — circle + vector primitives, no glyph font dependency)
  - svg_pixmap(svg, size): QSvgRenderer-backed rasteriser; dual-binding
    helper falls back from PySide6 to PyQt6.QtSvg.

Wired into ui_qt.py:
  - Conflict dialog: warning badge SVG (light disc + dark !), cloud
    card SVG, storage card SVG. Drops the QSS circle hack — entire
    badge is one pixmap now.
  - Login dialog: plus badge SVG (Prism-green disc + dark +).
  - Progress window: sync arrow badge SVG (Prism-green disc + circular
    arrow + head).

QSS shed: no more bg/border-radius/font-size acrobatics for badges
since the SVG includes the disc. Card icons are crisp at any DPI.

52 tests still green; pyz grows ~5 KB for the SVG strings + the new
icons module.
2026-06-05 01:05:54 +02:00

94 lines
3.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Inline SVG icons + Qt pixmap rasteriser.
Icons are stored as SVG strings so the pyz stays a single file (no
external assets). Rendering uses ``QSvgRenderer`` from whichever Qt
binding loaded — PySide6 or PyQt6.
Color baked into the SVG (Qt's QSvgRenderer ignores stylesheet
``currentColor`` inheritance). To recolour, edit the SVG string here
or call :func:`recolor` to substitute the placeholder ``#FFFFFF``.
Card glyphs from the Material Icons set (Apache-2.0). Badge glyphs
hand-rolled from pure SVG primitives so we don't pull a glyph font.
"""
from __future__ import annotations
from typing import Any
# ---------------------------------------------------------------------------
# Card glyphs (24x24, single-color, drawn over card bg)
# ---------------------------------------------------------------------------
# Material Icons "cloud" path data — outline of a stylised cumulus.
CLOUD_SVG = """<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#FFFFFF">
<path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96z"/>
</svg>"""
# Material Icons "storage" — three stacked server racks. Reads as
# "local storage" without the visual ambiguity of a floppy disk.
STORAGE_SVG = """<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#FFFFFF">
<path d="M2 20h20v-4H2v4zm2-3h2v2H4v-2zM2 4v4h20V4H2zm4 3H4V5h2v2zm-4 7h20v-4H2v4zm2-3h2v2H4v-2z"/>
</svg>"""
# ---------------------------------------------------------------------------
# Header badges (32x32, full disc + glyph)
# ---------------------------------------------------------------------------
# Light-gray disc with a dark exclamation. Used in the conflict dialog.
WARNING_BADGE_SVG = """<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<circle cx="16" cy="16" r="16" fill="#D6DDE6"/>
<rect x="14.5" y="7" width="3" height="11" rx="1.5" fill="#313131"/>
<circle cx="16" cy="22.5" r="1.8" fill="#313131"/>
</svg>"""
# Prism-green disc with a dark plus. Used in the login dialog.
PLUS_BADGE_SVG = """<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<circle cx="16" cy="16" r="16" fill="#96DB59"/>
<rect x="14.5" y="8" width="3" height="16" rx="1.5" fill="#1A1A1A"/>
<rect x="8" y="14.5" width="16" height="3" rx="1.5" fill="#1A1A1A"/>
</svg>"""
# Prism-green disc with a circular arrow. Used in the progress window.
SYNC_BADGE_SVG = """<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<circle cx="16" cy="16" r="16" fill="#96DB59"/>
<path d="M 23 17.5 A 7.5 7.5 0 1 1 22 11.5"
stroke="#1A1A1A" stroke-width="2.6" fill="none" stroke-linecap="round"/>
<polygon points="23.5,7.5 23.5,14 17.5,11.2" fill="#1A1A1A"/>
</svg>"""
# ---------------------------------------------------------------------------
# Rasterise
# ---------------------------------------------------------------------------
def svg_pixmap(svg: str, size: int) -> Any:
"""Render an SVG string to a ``QPixmap`` of ``size`` × ``size`` px.
Caller is responsible for keeping the returned pixmap alive (e.g.
by attaching it to a QLabel). Raises ``ImportError`` if no Qt
binding is available.
"""
QtCore, QtGui, QtSvg = _import_qt_for_svg()
pm = QtGui.QPixmap(size, size)
pm.fill(QtCore.Qt.GlobalColor.transparent)
renderer = QtSvg.QSvgRenderer(QtCore.QByteArray(svg.encode("utf-8")))
painter = QtGui.QPainter(pm)
try:
renderer.render(painter)
finally:
painter.end()
return pm
def _import_qt_for_svg() -> tuple[Any, Any, Any]:
try:
from PySide6 import QtCore, QtGui, QtSvg # type: ignore
return QtCore, QtGui, QtSvg
except ImportError:
from PyQt6 import QtCore, QtGui, QtSvg # type: ignore
return QtCore, QtGui, QtSvg