Add launcher icon generator
This commit is contained in:
@@ -4,167 +4,25 @@
|
|||||||
android:height="108dp"
|
android:height="108dp"
|
||||||
android:viewportWidth="108"
|
android:viewportWidth="108"
|
||||||
android:viewportHeight="108">
|
android:viewportHeight="108">
|
||||||
|
<!-- Base -->
|
||||||
<path
|
<path
|
||||||
android:fillColor="#3DDC84"
|
android:fillColor="#102B2F"
|
||||||
android:pathData="M0,0h108v108h-108z" />
|
android:pathData="M0,0 L108,0 L108,108 L0,108 Z" />
|
||||||
|
|
||||||
|
<!-- Soft curve -->
|
||||||
<path
|
<path
|
||||||
android:fillColor="#00000000"
|
android:fillColor="#163A40"
|
||||||
android:pathData="M9,0L9,108"
|
android:pathData="M0,0h108v44c-18,6 -34,12 -50,24c-18,13 -33,29 -58,34z" />
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
<!-- Diagonal accent -->
|
||||||
<path
|
<path
|
||||||
android:fillColor="#00000000"
|
android:fillColor="#1E6D70"
|
||||||
android:pathData="M19,0L19,108"
|
android:pathData="M74,-8h42v124h-72z"
|
||||||
android:strokeWidth="0.8"
|
android:fillAlpha="0.55" />
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
|
<!-- Bottom wave -->
|
||||||
<path
|
<path
|
||||||
android:fillColor="#00000000"
|
android:fillColor="#8DE6D5"
|
||||||
android:pathData="M29,0L29,108"
|
android:pathData="M0,82c17,-10 29,-14 45,-14c24,0 37,12 63,5v35h-108z"
|
||||||
android:strokeWidth="0.8"
|
android:fillAlpha="0.16" />
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M39,0L39,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M49,0L49,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M59,0L59,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M69,0L69,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M79,0L79,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M89,0L89,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M99,0L99,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,9L108,9"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,19L108,19"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,29L108,29"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,39L108,39"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,49L108,49"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,59L108,59"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,69L108,69"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,79L108,79"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,89L108,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,99L108,99"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,29L89,29"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,39L89,39"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,49L89,49"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,59L89,59"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,69L89,69"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,79L89,79"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M29,19L29,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M39,19L39,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M49,19L49,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M59,19L59,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M69,19L69,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M79,19L79,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -1,30 +1,44 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:aapt="http://schemas.android.com/aapt"
|
|
||||||
android:width="108dp"
|
android:width="108dp"
|
||||||
android:height="108dp"
|
android:height="108dp"
|
||||||
android:viewportWidth="108"
|
android:viewportWidth="108"
|
||||||
android:viewportHeight="108">
|
android:viewportHeight="108">
|
||||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
<!-- Bar shadow -->
|
||||||
<aapt:attr name="android:fillColor">
|
|
||||||
<gradient
|
|
||||||
android:endX="85.84757"
|
|
||||||
android:endY="92.4963"
|
|
||||||
android:startX="42.9492"
|
|
||||||
android:startY="49.59793"
|
|
||||||
android:type="linear">
|
|
||||||
<item
|
|
||||||
android:color="#44000000"
|
|
||||||
android:offset="0.0" />
|
|
||||||
<item
|
|
||||||
android:color="#00000000"
|
|
||||||
android:offset="1.0" />
|
|
||||||
</gradient>
|
|
||||||
</aapt:attr>
|
|
||||||
</path>
|
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FFFFFF"
|
android:fillColor="#000000"
|
||||||
android:fillType="nonZero"
|
android:pathData="M1,40 L109,40 A0,0 0,0 1 109,40 L109,72 A0,0 0,0 1 109,72 L1,72 A0,0 0,0 1 1,72 L1,40 A0,0 0,0 1 1,40 Z"
|
||||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
android:fillAlpha="0.33" />
|
||||||
android:strokeWidth="1"
|
|
||||||
android:strokeColor="#00000000" />
|
<!-- Bar -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#EFFFFB"
|
||||||
|
android:pathData="M0,38 L108,38 A0,0 0,0 1 108,38 L108,70 A0,0 0,0 1 108,70 L0,70 A0,0 0,0 1 0,70 L0,38 A0,0 0,0 1 0,38 Z" />
|
||||||
|
|
||||||
|
<!-- Back -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19.6795,54 L37,44 L37,64 Z"
|
||||||
|
android:strokeColor="#1B454A"
|
||||||
|
android:strokeWidth="6"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round" />
|
||||||
|
|
||||||
|
<!-- Home -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M44,54 A10,10 0,1 0 64,54 A10,10 0,1 0 44,54"
|
||||||
|
android:strokeColor="#1B454A"
|
||||||
|
android:strokeWidth="6"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round" />
|
||||||
|
|
||||||
|
<!-- Recents -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M71,44 L91,44 L91,64 L71,64 Z"
|
||||||
|
android:strokeColor="#1B454A"
|
||||||
|
android:strokeWidth="6"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round" />
|
||||||
</vector>
|
</vector>
|
||||||
@@ -0,0 +1,323 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Generate the NavButtons launcher icon and an SVG browser preview.
|
||||||
|
|
||||||
|
Edit the constants below, run this script, then open:
|
||||||
|
|
||||||
|
build/launcher-icon-preview.svg
|
||||||
|
|
||||||
|
The Android VectorDrawable files are generated from the same geometry, so the
|
||||||
|
browser preview and app icon stay in sync.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
from xml.sax.saxutils import escape
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
|
||||||
|
FOREGROUND = ROOT / "app/src/main/res/drawable/ic_launcher_foreground.xml"
|
||||||
|
BACKGROUND = ROOT / "app/src/main/res/drawable/ic_launcher_background.xml"
|
||||||
|
PREVIEW = ROOT / "build/launcher-icon-preview.svg"
|
||||||
|
|
||||||
|
VIEWPORT = 108
|
||||||
|
PREVIEW_SCALE = 4
|
||||||
|
|
||||||
|
# Horizontal white bar / pill.
|
||||||
|
BAR_X = 0
|
||||||
|
BAR_Y = 38
|
||||||
|
BAR_WIDTH = 108
|
||||||
|
BAR_HEIGHT = 32
|
||||||
|
BAR_RADIUS = 0
|
||||||
|
BAR_FILL = "#EFFFFB"
|
||||||
|
BAR_SHADOW_X = 1
|
||||||
|
BAR_SHADOW_Y = 2
|
||||||
|
BAR_SHADOW_FILL = "#000000"
|
||||||
|
BAR_SHADOW_ALPHA = 0.33
|
||||||
|
|
||||||
|
# Each symbol uses center x/y plus total width/height and stroke width.
|
||||||
|
# The Back icon is a hollow triangle, Home is a hollow circle, Recents is a
|
||||||
|
# hollow square. Coordinates are in the 108x108 adaptive-icon viewport.
|
||||||
|
ALL_SIZE = 20
|
||||||
|
|
||||||
|
BACK_X = 54-27
|
||||||
|
BACK_Y = 54
|
||||||
|
BACK_WIDTH = (ALL_SIZE**2 - (ALL_SIZE*0.5)**2)**0.5
|
||||||
|
BACK_HEIGHT = ALL_SIZE
|
||||||
|
BACK_STROKE = 6
|
||||||
|
BACK_X = BACK_X + (ALL_SIZE - BACK_WIDTH)*0.5
|
||||||
|
|
||||||
|
HOME_X = 54
|
||||||
|
HOME_Y = 54
|
||||||
|
HOME_WIDTH = ALL_SIZE
|
||||||
|
HOME_HEIGHT = ALL_SIZE
|
||||||
|
HOME_STROKE = 6
|
||||||
|
|
||||||
|
RECENTS_X = 54+27
|
||||||
|
RECENTS_Y = 54
|
||||||
|
RECENTS_WIDTH = ALL_SIZE
|
||||||
|
RECENTS_HEIGHT = ALL_SIZE
|
||||||
|
RECENTS_STROKE = 6
|
||||||
|
|
||||||
|
SYMBOL_COLOR = "#1B454A"
|
||||||
|
|
||||||
|
# Background.
|
||||||
|
BG_BASE = "#102B2F"
|
||||||
|
BG_CURVE = "#163A40"
|
||||||
|
BG_SLASH = "#1E6D70"
|
||||||
|
BG_SLASH_ALPHA = 0.55
|
||||||
|
BG_WAVE = "#8DE6D5"
|
||||||
|
BG_WAVE_ALPHA = 0.16
|
||||||
|
|
||||||
|
# Adaptive icon safe-zone preview. Android guarantees roughly the central
|
||||||
|
# 72x72 area survives masks, so this uses radius 36.
|
||||||
|
SAFE_RADIUS = 36
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Style:
|
||||||
|
fill: str = "#00000000"
|
||||||
|
fill_alpha: float | None = None
|
||||||
|
stroke: str | None = None
|
||||||
|
stroke_width: float | None = None
|
||||||
|
line_cap: str | None = None
|
||||||
|
line_join: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class IconPath:
|
||||||
|
data: str
|
||||||
|
style: Style
|
||||||
|
comment: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def fmt(value: float) -> str:
|
||||||
|
return f"{value:g}"
|
||||||
|
|
||||||
|
|
||||||
|
def path(
|
||||||
|
data: str,
|
||||||
|
fill: str = "#00000000",
|
||||||
|
*,
|
||||||
|
fill_alpha: float | None = None,
|
||||||
|
stroke: str | None = None,
|
||||||
|
stroke_width: float | None = None,
|
||||||
|
line_cap: str | None = None,
|
||||||
|
line_join: str | None = None,
|
||||||
|
comment: str | None = None,
|
||||||
|
) -> IconPath:
|
||||||
|
return IconPath(data, Style(fill, fill_alpha, stroke, stroke_width, line_cap, line_join), comment)
|
||||||
|
|
||||||
|
|
||||||
|
def rect(x: float, y: float, width: float, height: float) -> str:
|
||||||
|
return (
|
||||||
|
f"M{fmt(x)},{fmt(y)} "
|
||||||
|
f"L{fmt(x + width)},{fmt(y)} "
|
||||||
|
f"L{fmt(x + width)},{fmt(y + height)} "
|
||||||
|
f"L{fmt(x)},{fmt(y + height)} Z"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def round_rect(x: float, y: float, width: float, height: float, radius: float) -> str:
|
||||||
|
radius = min(radius, width / 2, height / 2)
|
||||||
|
return (
|
||||||
|
f"M{fmt(x + radius)},{fmt(y)} "
|
||||||
|
f"L{fmt(x + width - radius)},{fmt(y)} "
|
||||||
|
f"A{fmt(radius)},{fmt(radius)} 0,0 1 {fmt(x + width)},{fmt(y + radius)} "
|
||||||
|
f"L{fmt(x + width)},{fmt(y + height - radius)} "
|
||||||
|
f"A{fmt(radius)},{fmt(radius)} 0,0 1 {fmt(x + width - radius)},{fmt(y + height)} "
|
||||||
|
f"L{fmt(x + radius)},{fmt(y + height)} "
|
||||||
|
f"A{fmt(radius)},{fmt(radius)} 0,0 1 {fmt(x)},{fmt(y + height - radius)} "
|
||||||
|
f"L{fmt(x)},{fmt(y + radius)} "
|
||||||
|
f"A{fmt(radius)},{fmt(radius)} 0,0 1 {fmt(x + radius)},{fmt(y)} Z"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def circle(cx: float, cy: float, width: float, height: float) -> str:
|
||||||
|
rx = width / 2
|
||||||
|
ry = height / 2
|
||||||
|
return (
|
||||||
|
f"M{fmt(cx - rx)},{fmt(cy)} "
|
||||||
|
f"A{fmt(rx)},{fmt(ry)} 0,1 0 {fmt(cx + rx)},{fmt(cy)} "
|
||||||
|
f"A{fmt(rx)},{fmt(ry)} 0,1 0 {fmt(cx - rx)},{fmt(cy)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def triangle_left(cx: float, cy: float, width: float, height: float) -> str:
|
||||||
|
left = cx - width / 2
|
||||||
|
right = cx + width / 2
|
||||||
|
top = cy - height / 2
|
||||||
|
bottom = cy + height / 2
|
||||||
|
return f"M{fmt(left)},{fmt(cy)} L{fmt(right)},{fmt(top)} L{fmt(right)},{fmt(bottom)} Z"
|
||||||
|
|
||||||
|
|
||||||
|
def stroke_style(width: float) -> dict[str, object]:
|
||||||
|
return {
|
||||||
|
"stroke": SYMBOL_COLOR,
|
||||||
|
"stroke_width": width,
|
||||||
|
"line_cap": "round",
|
||||||
|
"line_join": "round",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def background_paths() -> list[IconPath]:
|
||||||
|
return [
|
||||||
|
path(rect(0, 0, VIEWPORT, VIEWPORT), BG_BASE, comment="Base"),
|
||||||
|
path(
|
||||||
|
"M0,0h108v44c-18,6 -34,12 -50,24c-18,13 -33,29 -58,34z",
|
||||||
|
BG_CURVE,
|
||||||
|
comment="Soft curve",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"M74,-8h42v124h-72z",
|
||||||
|
BG_SLASH,
|
||||||
|
fill_alpha=BG_SLASH_ALPHA,
|
||||||
|
comment="Diagonal accent",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"M0,82c17,-10 29,-14 45,-14c24,0 37,12 63,5v35h-108z",
|
||||||
|
BG_WAVE,
|
||||||
|
fill_alpha=BG_WAVE_ALPHA,
|
||||||
|
comment="Bottom wave",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def foreground_paths() -> list[IconPath]:
|
||||||
|
return [
|
||||||
|
path(
|
||||||
|
round_rect(BAR_X + BAR_SHADOW_X, BAR_Y + BAR_SHADOW_Y, BAR_WIDTH, BAR_HEIGHT, BAR_RADIUS),
|
||||||
|
BAR_SHADOW_FILL,
|
||||||
|
fill_alpha=BAR_SHADOW_ALPHA,
|
||||||
|
comment="Bar shadow",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
round_rect(BAR_X, BAR_Y, BAR_WIDTH, BAR_HEIGHT, BAR_RADIUS),
|
||||||
|
BAR_FILL,
|
||||||
|
comment="Bar",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
triangle_left(BACK_X, BACK_Y, BACK_WIDTH, BACK_HEIGHT),
|
||||||
|
comment="Back",
|
||||||
|
**stroke_style(BACK_STROKE),
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
circle(HOME_X, HOME_Y, HOME_WIDTH, HOME_HEIGHT),
|
||||||
|
comment="Home",
|
||||||
|
**stroke_style(HOME_STROKE),
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
rect(
|
||||||
|
RECENTS_X - RECENTS_WIDTH / 2,
|
||||||
|
RECENTS_Y - RECENTS_HEIGHT / 2,
|
||||||
|
RECENTS_WIDTH,
|
||||||
|
RECENTS_HEIGHT,
|
||||||
|
),
|
||||||
|
comment="Recents",
|
||||||
|
**stroke_style(RECENTS_STROKE),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def android_path_xml(icon_path: IconPath) -> str:
|
||||||
|
attrs = [
|
||||||
|
f'android:fillColor="{icon_path.style.fill}"',
|
||||||
|
f'android:pathData="{escape(icon_path.data)}"',
|
||||||
|
]
|
||||||
|
if icon_path.style.fill_alpha is not None:
|
||||||
|
attrs.append(f'android:fillAlpha="{fmt(icon_path.style.fill_alpha)}"')
|
||||||
|
if icon_path.style.stroke:
|
||||||
|
attrs.append(f'android:strokeColor="{icon_path.style.stroke}"')
|
||||||
|
if icon_path.style.stroke_width is not None:
|
||||||
|
attrs.append(f'android:strokeWidth="{fmt(icon_path.style.stroke_width)}"')
|
||||||
|
if icon_path.style.line_cap:
|
||||||
|
attrs.append(f'android:strokeLineCap="{icon_path.style.line_cap}"')
|
||||||
|
if icon_path.style.line_join:
|
||||||
|
attrs.append(f'android:strokeLineJoin="{icon_path.style.line_join}"')
|
||||||
|
return " <path\n " + "\n ".join(attrs) + " />"
|
||||||
|
|
||||||
|
|
||||||
|
def write_vector(destination: Path, paths: list[IconPath]) -> None:
|
||||||
|
body = "\n".join(
|
||||||
|
(f"\n <!-- {escape(item.comment)} -->\n" if item.comment else "\n")
|
||||||
|
+ android_path_xml(item)
|
||||||
|
for item in paths
|
||||||
|
)
|
||||||
|
destination.write_text(
|
||||||
|
'<?xml version="1.0" encoding="utf-8"?>\n'
|
||||||
|
'<vector xmlns:android="http://schemas.android.com/apk/res/android"\n'
|
||||||
|
' android:width="108dp"\n'
|
||||||
|
' android:height="108dp"\n'
|
||||||
|
' android:viewportWidth="108"\n'
|
||||||
|
' android:viewportHeight="108">'
|
||||||
|
f"{body}\n"
|
||||||
|
"</vector>\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def svg_path_xml(icon_path: IconPath) -> str:
|
||||||
|
attrs = [f'd="{escape(icon_path.data)}"', f'fill="{icon_path.style.fill}"']
|
||||||
|
if icon_path.style.fill_alpha is not None:
|
||||||
|
attrs.append(f'fill-opacity="{fmt(icon_path.style.fill_alpha)}"')
|
||||||
|
if icon_path.style.stroke:
|
||||||
|
attrs.append(f'stroke="{icon_path.style.stroke}"')
|
||||||
|
if icon_path.style.stroke_width is not None:
|
||||||
|
attrs.append(f'stroke-width="{fmt(icon_path.style.stroke_width)}"')
|
||||||
|
if icon_path.style.line_cap:
|
||||||
|
attrs.append(f'stroke-linecap="{icon_path.style.line_cap}"')
|
||||||
|
if icon_path.style.line_join:
|
||||||
|
attrs.append(f'stroke-linejoin="{icon_path.style.line_join}"')
|
||||||
|
return f'<path {" ".join(attrs)} />'
|
||||||
|
|
||||||
|
|
||||||
|
def icon_svg_body() -> str:
|
||||||
|
return "\n ".join(svg_path_xml(item) for item in background_paths() + foreground_paths())
|
||||||
|
|
||||||
|
|
||||||
|
def write_preview() -> None:
|
||||||
|
PREVIEW.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
body = icon_svg_body()
|
||||||
|
canvas_width = 360
|
||||||
|
canvas_height = 136
|
||||||
|
preview_width = canvas_width * PREVIEW_SCALE
|
||||||
|
preview_height = canvas_height * PREVIEW_SCALE
|
||||||
|
PREVIEW.write_text(
|
||||||
|
f'<svg xmlns="http://www.w3.org/2000/svg" width="{preview_width}" height="{preview_height}" '
|
||||||
|
f'viewBox="0 0 {canvas_width} {canvas_height}">\n'
|
||||||
|
" <defs>\n"
|
||||||
|
' <clipPath id="rounded-mask"><rect x="126" y="14" width="108" height="108" rx="27" /></clipPath>\n'
|
||||||
|
f' <clipPath id="circle-mask"><circle cx="306" cy="68" r="{SAFE_RADIUS}" /></clipPath>\n'
|
||||||
|
" </defs>\n"
|
||||||
|
' <rect width="360" height="136" fill="#0B1113" />\n'
|
||||||
|
' <g transform="translate(0,14)">\n'
|
||||||
|
f" {body}\n"
|
||||||
|
" </g>\n"
|
||||||
|
' <g clip-path="url(#rounded-mask)" transform="translate(126,14)">\n'
|
||||||
|
f" {body}\n"
|
||||||
|
" </g>\n"
|
||||||
|
' <rect x="126" y="14" width="108" height="108" rx="27" fill="none" stroke="#88FFFFFF" stroke-width="0.8" />\n'
|
||||||
|
' <g clip-path="url(#circle-mask)" transform="translate(252,14)">\n'
|
||||||
|
f" {body}\n"
|
||||||
|
" </g>\n"
|
||||||
|
f' <circle cx="306" cy="68" r="{SAFE_RADIUS}" fill="none" stroke="#88FFFFFF" stroke-width="0.8" />\n'
|
||||||
|
' <text x="54" y="132" fill="#D7E7E3" font-size="5" text-anchor="middle">full square</text>\n'
|
||||||
|
' <text x="180" y="132" fill="#D7E7E3" font-size="5" text-anchor="middle">rounded mask</text>\n'
|
||||||
|
' <text x="306" y="132" fill="#D7E7E3" font-size="5" text-anchor="middle">safe circle</text>\n'
|
||||||
|
"</svg>\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
write_vector(BACKGROUND, background_paths())
|
||||||
|
write_vector(FOREGROUND, foreground_paths())
|
||||||
|
write_preview()
|
||||||
|
print(f"Wrote {BACKGROUND.relative_to(ROOT)}")
|
||||||
|
print(f"Wrote {FOREGROUND.relative_to(ROOT)}")
|
||||||
|
print(f"Wrote {PREVIEW.relative_to(ROOT)}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user