feat: generate Swift design tokens from tokens.json and add SstUI base
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -58,6 +58,138 @@ print(f"{'text':16} {'on':20} {'finish':6} ratio")
|
||||
for fg, bg, theme, r, ok in rows:
|
||||
print(f"{fg:16} {bg:20} {theme:6} {r:5.2f} {'ok' if ok else 'FAIL'}")
|
||||
|
||||
# --- swift ------------------------------------------------------------------
|
||||
def camel(name):
|
||||
parts = name.replace("-", ".").split(".")
|
||||
return parts[0] + "".join(p[:1].upper() + p[1:] for p in parts[1:])
|
||||
|
||||
|
||||
def num(v):
|
||||
return repr(float(v)) if isinstance(v, float) else str(v)
|
||||
|
||||
|
||||
def doc(use):
|
||||
return " /// " + use.replace("\n", " ")
|
||||
|
||||
|
||||
def swift_source(t):
|
||||
o = []
|
||||
add = o.append
|
||||
add("// GENERATED by Design/build_tokens.py from Design/tokens.json. Do not edit by hand.")
|
||||
add("import SwiftUI\n")
|
||||
|
||||
add("public enum Palette {")
|
||||
for n, c in t["color"].items():
|
||||
add(doc(c["use"]))
|
||||
add(f" public static let {camel(n)} = Color.dynamic(light: 0x{c['light'][1:].upper()}, dark: 0x{c['dark'][1:].upper()})")
|
||||
add("}\n")
|
||||
|
||||
add("public enum Opacity {")
|
||||
for n, v in t["opacity"].items():
|
||||
add(doc(v["use"]))
|
||||
add(f" public static let {camel(n)}: Double = {num(v['value'])}")
|
||||
add("}\n")
|
||||
|
||||
add("public enum FontFamily: String {")
|
||||
for n, f in t["font"].items():
|
||||
add(f" case {n.replace('family.', '')} = \"{f['value']}\"")
|
||||
add("}\n")
|
||||
|
||||
add("public enum Typography {")
|
||||
for n, s in t["type"].items():
|
||||
add(doc(s["use"]))
|
||||
add(f" public static let {camel(n)} = TextStyle(family: .{s['family']}, size: {num(s['size'])}, weight: {s['weight']}, "
|
||||
f"tracking: {num(s['tracking'])}, line: {num(s['line'])}, caps: {'true' if s['caps'] else 'false'}, "
|
||||
f"tabular: {'true' if s.get('tabular') else 'false'})")
|
||||
add("}\n")
|
||||
|
||||
add("public enum Segment {")
|
||||
for n, v in t["segment"].items():
|
||||
add(doc(v["use"]))
|
||||
add(f" public static let {camel(n)}: CGFloat = {num(v['value'])}")
|
||||
add("}\n")
|
||||
|
||||
add("public enum Space {")
|
||||
for n, v in t["space"].items():
|
||||
add(f" public static let s{n}: CGFloat = {v}")
|
||||
add("}\n")
|
||||
|
||||
for enum, group in (("Layout", "layout"), ("Size", "size"), ("Radius", "radius"), ("Border", "border")):
|
||||
add(f"public enum {enum} {{")
|
||||
for n, v in t[group].items():
|
||||
add(doc(v["use"]))
|
||||
add(f" public static let {camel(n)}: CGFloat = {num(v['value'])}")
|
||||
add("}\n")
|
||||
|
||||
add("public enum Shadows {")
|
||||
for n, s in t["shadow"].items():
|
||||
add(doc(s["use"]))
|
||||
add(f" public static let {camel(n)}: [ShadowLayer] = [")
|
||||
for l in s["layers"]:
|
||||
add(f" ShadowLayer(inset: {'true' if l['inset'] else 'false'}, x: {l['x']}, y: {l['y']}, blur: {l['blur']}, spread: {l['spread']}, "
|
||||
f"hex: 0x{l['color'][1:].upper()}, alphaLight: {num(l['alpha']['light'])}, alphaDark: {num(l['alpha']['dark'])}),")
|
||||
add(" ]")
|
||||
if "translateY" in s:
|
||||
add(f" public static let {camel(n)}Offset: CGFloat = {s['translateY']}")
|
||||
add("}\n")
|
||||
|
||||
add("public enum Texture {")
|
||||
for n, v in t["texture"].items():
|
||||
add(f" public static let {camel(n)}GrainLight: Double = {num(v['grain']['light'])}")
|
||||
add(f" public static let {camel(n)}GrainDark: Double = {num(v['grain']['dark'])}")
|
||||
add("}\n")
|
||||
|
||||
add("public enum MeterTokens {")
|
||||
for n, v in t["meter"].items():
|
||||
val = v["value"]
|
||||
if n == "vu.ticks":
|
||||
add(doc(v["use"]))
|
||||
add(f" public static let vuTicks: [Int] = [{', '.join(str(int(x)) for x in val.split())}]")
|
||||
elif n == "led.segmentSize":
|
||||
w, h = val.split("x")
|
||||
add(doc(v["use"]))
|
||||
add(f" public static let ledSegmentWidth: CGFloat = {w}")
|
||||
add(f" public static let ledSegmentHeight: CGFloat = {h}")
|
||||
elif n == "peak.holdMs":
|
||||
add(doc(v["use"]))
|
||||
add(f" public static let peakHoldSeconds: Double = {num(val / 1000)}")
|
||||
elif n == "led.segments":
|
||||
add(doc(v["use"]))
|
||||
add(f" public static let ledSegments: Int = {val}")
|
||||
else:
|
||||
add(doc(v["use"]))
|
||||
add(f" public static let {camel(n)}: Double = {num(val)}")
|
||||
add("}\n")
|
||||
|
||||
add("public enum Motion {")
|
||||
for n, m in t["motion"].items():
|
||||
if n == "reducedMotion":
|
||||
continue
|
||||
add(doc(m["use"]))
|
||||
if m.get("unit") == "ms":
|
||||
add(f" public static let {camel(n)}: Double = {num(m['value'] / 1000)}")
|
||||
elif m.get("unit") in ("s", "ratio", "Hz"):
|
||||
add(f" public static let {camel(n)}: Double = {num(m['value'])}")
|
||||
elif isinstance(m["value"], str) and m["value"].startswith("cubic-bezier"):
|
||||
pts = [x.strip() for x in m["value"][m["value"].index("(") + 1:-1].split(",")]
|
||||
add(f" public static let {camel(n)}: (Double, Double, Double, Double) = ({', '.join(pts)})")
|
||||
add("}")
|
||||
return "\n".join(o) + "\n"
|
||||
|
||||
|
||||
swift_text = swift_source(tokens)
|
||||
swift_dir = ROOT / "Sources" / "SstUI"
|
||||
swift_path = swift_dir / "DesignTokens.swift"
|
||||
|
||||
if "--check" in sys.argv:
|
||||
in_sync = swift_path.exists() and swift_path.read_text() == swift_text
|
||||
print("DesignTokens.swift is " + ("in sync with tokens.json" if in_sync else "OUT OF DATE: run python3 Design/build_tokens.py"))
|
||||
sys.exit(0 if (in_sync and not failed) else 1)
|
||||
|
||||
if swift_dir.exists():
|
||||
swift_path.write_text(swift_text)
|
||||
print("wrote Sources/SstUI/DesignTokens.swift")
|
||||
|
||||
# --- markdown ---------------------------------------------------------------
|
||||
md = []
|
||||
add = md.append
|
||||
|
||||
Reference in New Issue
Block a user