#!/usr/bin/env python3 """Generate 2048x2048 Unity UI atlas matching Doc/design/UI_Pic_Set.md layout and the casual soft-flat adventure style of the reference UI kit.""" from __future__ import annotations import math from pathlib import Path from PIL import Image, ImageDraw, ImageFilter, ImageFont OUT = Path(__file__).resolve().parent / "ui_atlas_2048.png" SIZE = 2048 # --- palette (soft-flat casual / tropical adventure) --- CREAM = (245, 232, 210, 255) CREAM_DARK = (214, 188, 150, 255) CREAM_EDGE = (168, 132, 92, 255) WOOD = (176, 120, 72, 255) WOOD_DARK = (130, 82, 46, 255) WOOD_LIGHT = (210, 160, 105, 255) TEXT_BRN = (92, 58, 36, 255) WHITE = (255, 255, 255, 255) GREEN = (110, 196, 92, 255) GREEN_D = (72, 148, 62, 255) GREEN_L = (150, 220, 120, 255) BLUE = (92, 168, 230, 255) BLUE_D = (58, 120, 190, 255) BLUE_L = (140, 200, 245, 255) ORANGE = (245, 168, 72, 255) ORANGE_D = (210, 120, 40, 255) PINK = (235, 110, 130, 255) PINK_D = (195, 70, 95, 255) YELLOW = (245, 210, 80, 255) YELLOW_D = (210, 165, 40, 255) GREY = (160, 170, 185, 255) GREY_D = (120, 130, 145, 255) RED = (230, 75, 70, 255) RED_D = (180, 45, 45, 255) DIM = (20, 20, 30, 120) TRANSPARENT = (0, 0, 0, 0) def clamp(v: int) -> int: return max(0, min(255, v)) def shade(c, d: int): return (clamp(c[0] + d), clamp(c[1] + d), clamp(c[2] + d), c[3] if len(c) > 3 else 255) def round_rect(draw, box, r, fill, outline=None, width=2): draw.rounded_rectangle(box, radius=r, fill=fill, outline=outline, width=width) def soft_panel(img, box, fill, edge, r, border=4, highlight=True): """Draw a bubbly soft-flat panel with bottom shadow and top highlight.""" x0, y0, x1, y1 = box layer = Image.new("RGBA", img.size, TRANSPARENT) d = ImageDraw.Draw(layer) # bottom shadow round_rect(d, (x0 + 2, y0 + 4, x1 + 2, y1 + 4), r, shade(edge, -40), None, 0) round_rect(d, (x0, y0, x1, y1), r, edge, None, 0) round_rect(d, (x0 + border, y0 + border, x1 - border, y1 - border), max(2, r - border), fill, None, 0) if highlight: # soft top gloss strip gloss = Image.new("RGBA", img.size, TRANSPARENT) gd = ImageDraw.Draw(gloss) mid = y0 + (y1 - y0) // 3 gd.rounded_rectangle( (x0 + border + 2, y0 + border + 2, x1 - border - 2, mid), radius=max(2, r - border - 2), fill=(255, 255, 255, 55), ) gloss = gloss.filter(ImageFilter.GaussianBlur(2)) layer = Image.alpha_composite(layer, gloss) img.alpha_composite(layer) def pill_btn(img, box, fill, edge, r=None): x0, y0, x1, y1 = box h = y1 - y0 if r is None: r = h // 2 soft_panel(img, box, fill, edge, r, border=3, highlight=True) def draw_icon_simple(draw, kind, cx, cy, s, color=WHITE, outline=None): """Tiny symbolic icons for atlas.""" o = outline or shade(color, -60) half = s // 2 def line(*pts, w=3): draw.line(pts, fill=o, width=w) if kind == "x": line(cx - half, cy - half, cx + half, cy + half, w=4) line(cx + half, cy - half, cx - half, cy + half, w=4) elif kind == "check": line(cx - half, cy, cx - half // 3, cy + half, w=4) line(cx - half // 3, cy + half, cx + half, cy - half, w=4) elif kind == "back": line(cx + half // 2, cy - half, cx - half, cy, w=4) line(cx - half, cy, cx + half // 2, cy + half, w=4) elif kind == "home": pts = [ (cx, cy - half), (cx + half, cy), (cx + half * 2 // 3, cy), (cx + half * 2 // 3, cy + half), (cx - half * 2 // 3, cy + half), (cx - half * 2 // 3, cy), (cx - half, cy), ] draw.polygon(pts, fill=color, outline=o) elif kind == "search": draw.ellipse((cx - half, cy - half, cx + half // 2, cy + half // 2), outline=o, width=3) line(cx + half // 3, cy + half // 3, cx + half, cy + half, w=3) elif kind == "clear": draw.ellipse((cx - half, cy - half, cx + half, cy + half), fill=shade(PINK, 20), outline=o, width=2) line(cx - half // 2, cy - half // 2, cx + half // 2, cy + half // 2, w=3) line(cx + half // 2, cy - half // 2, cx - half // 2, cy + half // 2, w=3) elif kind == "copy": draw.rectangle((cx - half + 4, cy - half + 2, cx + half // 2, cy + half // 2), outline=o, width=2) draw.rectangle((cx - half // 2, cy - half // 2, cx + half, cy + half), outline=o, width=2) elif kind == "paste": draw.rectangle((cx - half, cy - half // 2, cx + half, cy + half), outline=o, width=2) draw.rectangle((cx - half // 2, cy - half, cx + half // 2, cy - half // 3), outline=o, width=2) elif kind == "keyboard": draw.rounded_rectangle((cx - half, cy - half // 2, cx + half, cy + half // 2), 4, outline=o, width=2) for i in range(3): for j in range(2): draw.rectangle( (cx - half + 6 + i * 10, cy - half // 2 + 6 + j * 10, cx - half + 12 + i * 10, cy - half // 2 + 12 + j * 10), fill=o, ) elif kind == "arrow_down": draw.polygon( [(cx - half, cy - half // 3), (cx + half, cy - half // 3), (cx, cy + half // 2)], fill=o, ) elif kind == "plus": line(cx - half, cy, cx + half, cy, w=4) line(cx, cy - half, cx, cy + half, w=4) elif kind == "minus": line(cx - half, cy, cx + half, cy, w=4) elif kind == "lock": draw.rounded_rectangle((cx - half, cy - half // 4, cx + half, cy + half), 4, fill=color, outline=o, width=2) draw.arc((cx - half // 2, cy - half, cx + half // 2, cy), 0, 180, fill=o, width=3) elif kind == "unlock": draw.rounded_rectangle((cx - half, cy - half // 4, cx + half, cy + half), 4, fill=color, outline=o, width=2) draw.arc((cx, cy - half, cx + half, cy), 200, 360, fill=o, width=3) elif kind == "warning": draw.polygon([(cx, cy - half), (cx + half, cy + half), (cx - half, cy + half)], fill=YELLOW, outline=o) draw.ellipse((cx - 3, cy + half // 3, cx + 3, cy + half // 3 + 6), fill=o) draw.rectangle((cx - 2, cy - half // 3, cx + 2, cy + half // 4), fill=o) elif kind == "info": draw.ellipse((cx - half, cy - half, cx + half, cy + half), fill=BLUE, outline=o, width=2) draw.ellipse((cx - 3, cy - half // 2, cx + 3, cy - half // 2 + 6), fill=WHITE) draw.rectangle((cx - 2, cy - half // 6, cx + 2, cy + half // 2), fill=WHITE) elif kind == "server": for i in range(3): yy = cy - half + i * (s // 3) draw.rounded_rectangle((cx - half, yy, cx + half, yy + s // 4), 3, fill=BLUE_L, outline=o, width=1) elif kind == "user": draw.ellipse((cx - half // 2, cy - half, cx + half // 2, cy), fill=color, outline=o, width=2) draw.pieslice((cx - half, cy, cx + half, cy + half * 2), 200, 340, fill=color, outline=o) elif kind == "chat": draw.rounded_rectangle((cx - half, cy - half, cx + half, cy + half // 3), 8, fill=color, outline=o, width=2) draw.polygon([(cx - half // 2, cy + half // 4), (cx - half, cy + half), (cx, cy + half // 4)], fill=color, outline=o) elif kind == "mail": draw.rounded_rectangle((cx - half, cy - half // 2, cx + half, cy + half // 2), 4, outline=o, width=2) line(cx - half, cy - half // 2, cx, cy, w=2) line(cx + half, cy - half // 2, cx, cy, w=2) elif kind == "settings": draw.ellipse((cx - half // 2, cy - half // 2, cx + half // 2, cy + half // 2), outline=o, width=3) for a in range(0, 360, 45): rad = math.radians(a) x2 = cx + int(math.cos(rad) * half) y2 = cy + int(math.sin(rad) * half) line(cx, cy, x2, y2, w=3) elif kind == "sound_on": draw.polygon([(cx - half, cy - half // 3), (cx - half // 3, cy - half // 3), (cx + half // 3, cy - half), (cx + half // 3, cy + half), (cx - half // 3, cy + half // 3), (cx - half, cy + half // 3)], fill=color, outline=o) draw.arc((cx, cy - half // 2, cx + half, cy + half // 2), -60, 60, fill=o, width=2) elif kind == "sound_off": draw_icon_simple(draw, "sound_on", cx, cy, s, color, o) line(cx - half, cy - half, cx + half, cy + half, w=3) elif kind == "scan": draw.rectangle((cx - half, cy - half, cx - half // 3, cy - half // 3), outline=o, width=3) draw.rectangle((cx + half // 3, cy - half, cx + half, cy - half // 3), outline=o, width=3) draw.rectangle((cx - half, cy + half // 3, cx - half // 3, cy + half), outline=o, width=3) draw.rectangle((cx + half // 3, cy + half // 3, cx + half, cy + half), outline=o, width=3) elif kind == "refresh": draw.arc((cx - half, cy - half, cx + half, cy + half), 40, 300, fill=o, width=3) draw.polygon([(cx + half // 2, cy - half), (cx + half, cy - half // 3), (cx + half // 4, cy - half // 3)], fill=o) elif kind == "download": line(cx, cy - half, cx, cy + half // 3, w=3) line(cx - half // 2, cy, cx, cy + half // 2, w=3) line(cx + half // 2, cy, cx, cy + half // 2, w=3) line(cx - half, cy + half, cx + half, cy + half, w=3) elif kind == "upload": line(cx, cy + half // 3, cx, cy - half, w=3) line(cx - half // 2, cy - half // 3, cx, cy - half, w=3) line(cx + half // 2, cy - half // 3, cx, cy - half, w=3) line(cx - half, cy + half, cx + half, cy + half, w=3) elif kind == "bag": draw.rounded_rectangle((cx - half, cy - half // 3, cx + half, cy + half), 6, fill=ORANGE, outline=o, width=2) draw.arc((cx - half // 2, cy - half, cx + half // 2, cy), 0, 180, fill=o, width=2) elif kind == "item": draw.rounded_rectangle((cx - half, cy - half, cx + half, cy + half), 8, fill=BLUE_L, outline=o, width=2) draw.ellipse((cx - half // 3, cy - half // 3, cx + half // 3, cy + half // 3), fill=YELLOW) elif kind == "sword": line(cx, cy + half, cx, cy - half, w=4) line(cx - half // 2, cy - half // 2, cx + half // 2, cy - half // 2, w=3) draw.ellipse((cx - 4, cy + half - 4, cx + 4, cy + half + 4), fill=o) elif kind == "shield": draw.polygon( [(cx, cy - half), (cx + half, cy - half // 3), (cx + half * 2 // 3, cy + half), (cx, cy + half // 2), (cx - half * 2 // 3, cy + half), (cx - half, cy - half // 3)], fill=BLUE, outline=o, ) elif kind == "map": draw.polygon([(cx - half, cy - half // 2), (cx - half // 3, cy - half), (cx + half // 3, cy - half // 2), (cx + half, cy - half), (cx + half, cy + half // 2), (cx + half // 3, cy + half), (cx - half // 3, cy + half // 2), (cx - half, cy + half)], fill=GREEN_L, outline=o) elif kind == "star": pts = [] for i in range(5): a = math.radians(-90 + i * 72) pts.append((cx + int(math.cos(a) * half), cy + int(math.sin(a) * half))) a2 = math.radians(-90 + i * 72 + 36) pts.append((cx + int(math.cos(a2) * half // 2), cy + int(math.sin(a2) * half // 2))) draw.polygon(pts, fill=YELLOW, outline=o) elif kind == "heart": draw.ellipse((cx - half, cy - half // 2, cx, cy + half // 3), fill=PINK, outline=o) draw.ellipse((cx, cy - half // 2, cx + half, cy + half // 3), fill=PINK, outline=o) draw.polygon([(cx - half, cy), (cx + half, cy), (cx, cy + half)], fill=PINK, outline=o) elif kind == "diamond": draw.polygon([(cx, cy - half), (cx + half, cy), (cx, cy + half), (cx - half, cy)], fill=BLUE_L, outline=o) elif kind == "key": draw.ellipse((cx - half, cy - half // 2, cx - half // 6, cy + half // 2), outline=o, width=3) line(cx - half // 4, cy, cx + half, cy, w=3) line(cx + half // 2, cy, cx + half // 2, cy + half // 2, w=3) elif kind == "clock": draw.ellipse((cx - half, cy - half, cx + half, cy + half), outline=o, width=2) line(cx, cy, cx, cy - half // 2, w=2) line(cx, cy, cx + half // 2, cy + half // 4, w=2) elif kind == "calendar": draw.rounded_rectangle((cx - half, cy - half // 2, cx + half, cy + half), 4, outline=o, width=2) line(cx - half // 2, cy - half, cx - half // 2, cy - half // 3, w=2) line(cx + half // 2, cy - half, cx + half // 2, cy - half // 3, w=2) line(cx - half, cy - half // 6, cx + half, cy - half // 6, w=2) elif kind == "coin": draw.ellipse((cx - half, cy - half, cx + half, cy + half), fill=YELLOW, outline=ORANGE_D, width=3) draw.ellipse((cx - half // 2, cy - half // 2, cx + half // 2, cy + half // 2), outline=ORANGE_D, width=2) elif kind == "play": draw.polygon([(cx - half // 2, cy - half), (cx + half, cy), (cx - half // 2, cy + half)], fill=WHITE, outline=o) def try_font(size: int): for name in ( "C:/Windows/Fonts/msyh.ttc", "C:/Windows/Fonts/segoeui.ttf", "C:/Windows/Fonts/arial.ttf", ): try: return ImageFont.truetype(name, size) except OSError: continue return ImageFont.load_default() def place(img, name, x, y, w, h, painter): """Paint a slice; painter(sub_img, draw, w, h).""" sub = Image.new("RGBA", (w, h), TRANSPARENT) d = ImageDraw.Draw(sub) painter(sub, d, w, h) img.paste(sub, (x, y), sub) def main(): img = Image.new("RGBA", (SIZE, SIZE), (0, 0, 0, 0)) # subtle checker to show transparency zones (very faint, optional) — keep fully transparent for Unity font_sm = try_font(18) font_md = try_font(22) font_lg = try_font(28) # ---------- panels ---------- def p_main(sub, d, w, h): soft_panel(sub, (0, 0, w - 1, h - 1), CREAM, CREAM_EDGE, 40, 6) # wood header soft_panel(sub, (8, 8, w - 9, 72), WOOD_LIGHT, WOOD_DARK, 28, 4) d.text((w // 2, 40), "Title", font=font_md, fill=WHITE, anchor="mm") place(img, "ui_panel_main", 8, 8, 512, 384, p_main) def p_popup(sub, d, w, h): soft_panel(sub, (0, 0, w - 1, h - 1), CREAM, CREAM_EDGE, 32, 5) soft_panel(sub, (8, 8, w - 9, 56), BLUE_L, BLUE_D, 22, 3) d.text((w // 2, 36), "Popup", font=font_sm, fill=WHITE, anchor="mm") place(img, "ui_panel_popup", 528, 8, 384, 256, p_popup) def p_content(sub, d, w, h): soft_panel(sub, (0, 0, w - 1, h - 1), shade(CREAM, 10), CREAM_DARK, 28, 4) place(img, "ui_panel_content", 920, 8, 384, 256, p_content) def p_header(sub, d, w, h): soft_panel(sub, (0, 0, w - 1, h - 1), WOOD_LIGHT, WOOD_DARK, 24, 4) d.text((w // 2, h // 2), "Header", font=font_md, fill=WHITE, anchor="mm") place(img, "ui_panel_header", 1312, 8, 512, 96, p_header) def p_footer(sub, d, w, h): soft_panel(sub, (0, 0, w - 1, h - 1), shade(CREAM, -5), CREAM_EDGE, 24, 4) place(img, "ui_panel_footer", 1312, 112, 512, 96, p_footer) def p_tooltip(sub, d, w, h): soft_panel(sub, (0, 0, w - 1, h - 1), (255, 250, 235, 255), CREAM_EDGE, 22, 4) place(img, "ui_panel_tooltip", 528, 272, 320, 160, p_tooltip) def list_item(fill, edge): def _p(sub, d, w, h): soft_panel(sub, (0, 0, w - 1, h - 1), fill, edge, 20, 3) return _p place(img, "ui_list_item_normal", 856, 272, 384, 96, list_item(CREAM, CREAM_DARK)) place(img, "ui_list_item_selected", 1248, 272, 384, 96, list_item(BLUE_L, BLUE_D)) place(img, "ui_list_item_disabled", 1640, 272, 384, 96, list_item(GREY, GREY_D)) def overlay(sub, d, w, h): d.rectangle((0, 0, w, h), fill=DIM) place(img, "ui_overlay_dim", 920, 376, 256, 256, overlay) def div_h(sub, d, w, h): soft_panel(sub, (0, 2, w - 1, h - 1), CREAM_DARK, CREAM_EDGE, 4, 1, False) place(img, "ui_divider_h", 1184, 376, 512, 16, div_h) def div_v(sub, d, w, h): soft_panel(sub, (2, 0, w - 1, h - 1), CREAM_DARK, CREAM_EDGE, 4, 1, False) place(img, "ui_divider_v", 1704, 376, 16, 256, div_v) def badge(fill, edge): def _p(sub, d, w, h): soft_panel(sub, (0, 0, w - 1, h - 1), fill, edge, h // 2, 3) d.text((w // 2, h // 2), "99", font=font_sm, fill=WHITE, anchor="mm") return _p place(img, "ui_badge_base", 1728, 376, 128, 64, badge(BLUE, BLUE_D)) place(img, "ui_badge_alert", 1864, 376, 128, 64, badge(RED, RED_D)) # ---------- buttons ---------- def btn(fill, edge, label=None, pressed=False, disabled=False): def _p(sub, d, w, h): f, e = fill, edge if disabled: f, e = GREY, GREY_D elif pressed: f, e = shade(fill, -25), shade(edge, -25) soft_panel(sub, (0, 0 if not pressed else 2, w - 1, h - 1 if not pressed else h - 3), f, e, h // 2, 3) if label: d.text((w // 2, h // 2), label, font=font_md, fill=WHITE if not disabled else shade(WHITE, -40), anchor="mm") return _p place(img, "btn_primary_normal", 8, 648, 256, 96, btn(GREEN, GREEN_D, "OK")) place(img, "btn_primary_hover", 272, 648, 256, 96, btn(GREEN_L, GREEN, "OK")) place(img, "btn_primary_pressed", 536, 648, 256, 96, btn(GREEN, GREEN_D, "OK", pressed=True)) place(img, "btn_primary_disabled", 800, 648, 256, 96, btn(GREEN, GREEN_D, "OK", disabled=True)) place(img, "btn_secondary_normal", 1064, 648, 256, 96, btn(BLUE, BLUE_D, "Cancel")) place(img, "btn_secondary_pressed", 1328, 648, 256, 96, btn(BLUE, BLUE_D, "Cancel", pressed=True)) place(img, "btn_warning_normal", 1592, 648, 224, 96, btn(PINK, PINK_D, "Warn")) place(img, "btn_warning_pressed", 1816, 648, 224, 96, btn(PINK, PINK_D, "Warn", pressed=True)) place(img, "btn_small_normal", 8, 752, 192, 72, btn(ORANGE, ORANGE_D, "Small")) place(img, "btn_small_hover", 208, 752, 192, 72, btn(shade(ORANGE, 20), ORANGE, "Small")) place(img, "btn_small_pressed", 408, 752, 192, 72, btn(ORANGE, ORANGE_D, "Small", pressed=True)) place(img, "btn_small_disabled", 608, 752, 192, 72, btn(ORANGE, ORANGE_D, "Small", disabled=True)) def tab(active): def _p(sub, d, w, h): fill, edge = (BLUE_L, BLUE_D) if active else (CREAM, CREAM_EDGE) soft_panel(sub, (0, 8 if not active else 0, w - 1, h - 1), fill, edge, 22, 3) d.text((w // 2, h // 2 + (4 if not active else 0)), "Tab", font=font_sm, fill=WHITE if active else TEXT_BRN, anchor="mm") return _p place(img, "tab_active", 808, 752, 224, 80, tab(True)) place(img, "tab_inactive", 1040, 752, 224, 80, tab(False)) def seg(side, active=False): def _p(sub, d, w, h): fill = BLUE if active else CREAM edge = BLUE_D if active else CREAM_EDGE if side == "left": r = (28, 8, 28, 8) elif side == "right": r = (8, 28, 28, 8) else: r = 8 # approximate with rounded_rectangle soft_panel(sub, (0, 0, w - 1, h - 1), fill, edge, 16 if side == "mid" else 24, 3) d.text((w // 2, h // 2), side[0].upper(), font=font_sm, fill=WHITE if active else TEXT_BRN, anchor="mm") return _p place(img, "seg_left", 1272, 752, 160, 80, seg("left", True)) place(img, "seg_mid", 1440, 752, 160, 80, seg("mid", False)) place(img, "seg_right", 1608, 752, 160, 80, seg("right", False)) def icon_btn(kind, fill=RED, edge=RED_D): def _p(sub, d, w, h): soft_panel(sub, (0, 0, w - 1, h - 1), fill, edge, 18, 3) draw_icon_simple(d, kind, w // 2, h // 2, 18, WHITE, shade(edge, -20)) return _p place(img, "btn_close", 1776, 752, 80, 80, icon_btn("x", RED, RED_D)) place(img, "btn_back", 1864, 752, 80, 80, icon_btn("back", BLUE, BLUE_D)) place(img, "btn_home", 1952, 752, 80, 80, icon_btn("home", GREEN, GREEN_D)) # ---------- input ---------- def input_bg(edge_extra=None, focus=False, error=False): def _p(sub, d, w, h): fill = WHITE edge = CREAM_EDGE if focus: edge = BLUE_D if error: edge = PINK_D soft_panel(sub, (0, 0, w - 1, h - 1), fill, edge, 24, 3) d.text((24, h // 2), "Input...", font=font_sm, fill=GREY_D, anchor="lm") return _p place(img, "input_bg_normal", 8, 840, 384, 80, input_bg()) place(img, "input_bg_focus", 400, 840, 384, 80, input_bg(focus=True)) place(img, "input_bg_error", 792, 840, 384, 80, input_bg(error=True)) def dropdown(sub, d, w, h): soft_panel(sub, (0, 0, w - 1, h - 1), WHITE, CREAM_EDGE, 24, 3) d.text((24, h // 2), "Select", font=font_sm, fill=TEXT_BRN, anchor="lm") draw_icon_simple(d, "arrow_down", w - 36, h // 2, 14, TEXT_BRN, TEXT_BRN) place(img, "dropdown_bg", 1184, 840, 384, 80, dropdown) def arrow(sub, d, w, h): soft_panel(sub, (0, 0, w - 1, h - 1), BLUE, BLUE_D, 12, 2) draw_icon_simple(d, "arrow_down", w // 2, h // 2, 14, WHITE, WHITE) place(img, "dropdown_arrow", 1576, 840, 64, 64, arrow) def caret(sub, d, w, h): d.rectangle((1, 4, w - 1, h - 4), fill=BLUE_D) place(img, "input_caret", 1648, 840, 8, 64, caret) for name, x, kind in ( ("ico_keyboard", 1664, "keyboard"), ("ico_search", 1736, "search"), ("ico_clear", 1808, "clear"), ("ico_copy", 1880, "copy"), ("ico_paste", 1952, "paste"), ): def make_ico(k): def _p(sub, d, w, h): soft_panel(sub, (0, 0, w - 1, h - 1), CREAM, CREAM_EDGE, 14, 2) draw_icon_simple(d, k, w // 2, h // 2, 16, TEXT_BRN, TEXT_BRN) return _p place(img, name, x, 840, 64, 64, make_ico(kind)) # ---------- progress / slider / scrollbar ---------- def progress_bg(sub, d, w, h): soft_panel(sub, (0, 0, w - 1, h - 1), shade(CREAM, -15), CREAM_EDGE, h // 2, 2, False) place(img, "progress_bg", 8, 928, 512, 40, progress_bg) def progress_fill(fill, edge): def _p(sub, d, w, h): soft_panel(sub, (0, 0, w - 1, h - 1), fill, edge, h // 2, 2) return _p place(img, "progress_fill_blue", 528, 928, 512, 40, progress_fill(BLUE, BLUE_D)) place(img, "progress_fill_green", 1048, 928, 512, 40, progress_fill(GREEN, GREEN_D)) place(img, "progress_fill_warn", 1568, 928, 472, 40, progress_fill(ORANGE, ORANGE_D)) def slider_track(sub, d, w, h): soft_panel(sub, (0, 4, w - 1, h - 1), GREY, GREY_D, 10, 2, False) place(img, "slider_track", 8, 976, 512, 32, slider_track) def slider_fill(sub, d, w, h): soft_panel(sub, (0, 4, w - 1, h - 1), BLUE, BLUE_D, 10, 2) place(img, "slider_fill", 528, 976, 512, 32, slider_fill) def slider_handle(sub, d, w, h): soft_panel(sub, (4, 4, w - 5, h - 5), WHITE, BLUE_D, w // 2, 3) d.ellipse((w // 2 - 8, h // 2 - 8, w // 2 + 8, h // 2 + 8), fill=BLUE_L) place(img, "slider_handle", 1048, 976, 64, 64, slider_handle) def sb_track_v(sub, d, w, h): soft_panel(sub, (4, 0, w - 5, h - 1), shade(CREAM, -10), CREAM_DARK, 10, 2, False) place(img, "scrollbar_track_v", 1120, 976, 32, 512, sb_track_v) def sb_thumb_v(sub, d, w, h): soft_panel(sub, (2, 0, w - 3, h - 1), BLUE, BLUE_D, 12, 2) place(img, "scrollbar_thumb_v", 1160, 976, 32, 128, sb_thumb_v) def sb_track_h(sub, d, w, h): soft_panel(sub, (0, 4, w - 1, h - 5), shade(CREAM, -10), CREAM_DARK, 10, 2, False) place(img, "scrollbar_track_h", 1200, 976, 512, 32, sb_track_h) def sb_thumb_h(sub, d, w, h): soft_panel(sub, (0, 2, w - 1, h - 3), BLUE, BLUE_D, 12, 2) place(img, "scrollbar_thumb_h", 1720, 976, 128, 32, sb_thumb_h) def loading(frame): def _p(sub, d, w, h): soft_panel(sub, (2, 2, w - 3, h - 3), CREAM, CREAM_EDGE, w // 2, 2, False) bbox = (8, 8, w - 9, h - 9) start = frame * 180 d.arc(bbox, start, start + 270, fill=BLUE_D, width=6) return _p place(img, "loading_ring_0", 1856, 976, 64, 64, loading(0)) place(img, "loading_ring_1", 1928, 976, 64, 64, loading(1)) # ---------- toggle / checkbox / radio / status ---------- def toggle_bg(on): def _p(sub, d, w, h): fill, edge = (GREEN, GREEN_D) if on else (GREY, GREY_D) soft_panel(sub, (0, 0, w - 1, h - 1), fill, edge, h // 2, 3) return _p place(img, "toggle_bg_off", 8, 1048, 112, 56, toggle_bg(False)) place(img, "toggle_bg_on", 128, 1048, 112, 56, toggle_bg(True)) def knob(sub, d, w, h): soft_panel(sub, (0, 0, w - 1, h - 1), WHITE, CREAM_EDGE, w // 2, 2) place(img, "toggle_knob", 248, 1048, 48, 48, knob) def checkbox(on): def _p(sub, d, w, h): soft_panel(sub, (0, 0, w - 1, h - 1), GREEN if on else WHITE, GREEN_D if on else CREAM_EDGE, 12, 3) if on: draw_icon_simple(d, "check", w // 2, h // 2, 16, WHITE, WHITE) return _p place(img, "checkbox_off", 304, 1048, 64, 64, checkbox(False)) place(img, "checkbox_on", 376, 1048, 64, 64, checkbox(True)) def radio(on): def _p(sub, d, w, h): soft_panel(sub, (0, 0, w - 1, h - 1), WHITE, BLUE_D if on else CREAM_EDGE, w // 2, 3) if on: d.ellipse((16, 16, w - 17, h - 17), fill=BLUE) return _p place(img, "radio_off", 448, 1048, 64, 64, radio(False)) place(img, "radio_on", 520, 1048, 64, 64, radio(True)) status_icons = [ ("ico_check", 592, "check", GREEN), ("ico_lock", 664, "lock", ORANGE), ("ico_unlock", 736, "unlock", GREEN), ("ico_warning", 808, "warning", YELLOW), ("ico_info", 880, "info", BLUE), ("ico_plus", 952, "plus", GREEN), ("ico_minus", 1024, "minus", BLUE), ] for name, x, kind, col in status_icons: def make_st(k, c): def _p(sub, d, w, h): soft_panel(sub, (0, 0, w - 1, h - 1), shade(c, 40), shade(c, -40), 14, 2) draw_icon_simple(d, k, w // 2, h // 2, 16, WHITE if k != "warning" else TEXT_BRN, shade(c, -60)) return _p place(img, name, x, 1048, 64, 64, make_st(kind, col)) def slot(kind): def _p(sub, d, w, h): if kind == "empty": soft_panel(sub, (0, 0, w - 1, h - 1), shade(CREAM, 15), CREAM_EDGE, 16, 3) elif kind == "selected": soft_panel(sub, (0, 0, w - 1, h - 1), shade(YELLOW, 30), ORANGE_D, 16, 3) else: soft_panel(sub, (0, 0, w - 1, h - 1), GREY, GREY_D, 16, 3) draw_icon_simple(d, "lock", w // 2, h // 2, 18, WHITE, TEXT_BRN) return _p place(img, "slot_empty", 1200, 1048, 96, 96, slot("empty")) place(img, "slot_selected", 1304, 1048, 96, 96, slot("selected")) place(img, "slot_locked", 1408, 1048, 96, 96, slot("locked")) def avatar(sub, d, w, h): soft_panel(sub, (0, 0, w - 1, h - 1), WOOD_LIGHT, WOOD_DARK, w // 2, 4) soft_panel(sub, (16, 16, w - 17, h - 17), BLUE_L, BLUE_D, (w - 32) // 2, 3) draw_icon_simple(d, "user", w // 2, h // 2, 28, WHITE, WOOD_DARK) place(img, "avatar_frame", 1512, 1048, 128, 128, avatar) def medal(rank): colors = {1: (YELLOW, ORANGE_D), 2: (GREY, GREY_D), 3: (ORANGE, ORANGE_D)} def _p(sub, d, w, h): fill, edge = colors[rank] soft_panel(sub, (8, 0, w - 9, h - 17), fill, edge, w // 2 - 8, 3) d.text((w // 2, h // 2 - 6), str(rank), font=font_lg, fill=WHITE, anchor="mm") d.polygon([(w // 2 - 10, h - 20), (w // 2, h - 4), (w // 2 + 10, h - 20)], fill=edge) return _p place(img, "rank_medal_1", 1648, 1048, 80, 80, medal(1)) place(img, "rank_medal_2", 1736, 1048, 80, 80, medal(2)) place(img, "rank_medal_3", 1824, 1048, 80, 80, medal(3)) def coin(sub, d, w, h): soft_panel(sub, (4, 4, w - 5, h - 5), YELLOW, ORANGE_D, w // 2, 3) draw_icon_simple(d, "coin", w // 2, h // 2, 22, YELLOW, ORANGE_D) place(img, "ico_coin", 1912, 1048, 80, 80, coin) # ---------- menu / toast / business icons ---------- def menu_bg(sub, d, w, h): soft_panel(sub, (0, 0, w - 1, h - 1), CREAM, CREAM_EDGE, 28, 5) place(img, "menu_bg", 8, 1120, 384, 320, menu_bg) def menu_item(fill, edge): def _p(sub, d, w, h): soft_panel(sub, (0, 0, w - 1, h - 1), fill, edge, 14, 2) return _p place(img, "menu_item_normal", 400, 1120, 384, 64, menu_item(CREAM, CREAM_DARK)) place(img, "menu_item_hover", 400, 1192, 384, 64, menu_item(BLUE_L, BLUE_D)) place(img, "menu_item_disabled", 400, 1264, 384, 64, menu_item(GREY, GREY_D)) def toast(fill, edge, label): def _p(sub, d, w, h): soft_panel(sub, (0, 0, w - 1, h - 1), fill, edge, 28, 4) d.text((w // 2, h // 2), label, font=font_sm, fill=WHITE, anchor="mm") return _p place(img, "toast_bg", 792, 1120, 320, 96, toast(shade(TEXT_BRN, 40), TEXT_BRN, "Toast")) place(img, "notice_bg", 792, 1224, 320, 96, toast(BLUE, BLUE_D, "Notice")) def drag(sub, d, w, h): soft_panel(sub, (0, 4, w - 1, h - 5), GREY, GREY_D, 8, 2, False) for i in range(3): xx = w // 2 - 20 + i * 16 d.ellipse((xx, h // 2 - 4, xx + 8, h // 2 + 4), fill=WHITE) place(img, "drag_handle", 792, 1328, 160, 32, drag) def qr(sub, d, w, h): draw_icon_simple(d, "scan", w // 2, h // 2, 40, BLUE_D, BLUE_D) # corner brackets thicker soft_panel(sub, (0, 0, 28, 8), BLUE, BLUE_D, 2, 1, False) soft_panel(sub, (0, 0, 8, 28), BLUE, BLUE_D, 2, 1, False) soft_panel(sub, (w - 29, 0, w - 1, 8), BLUE, BLUE_D, 2, 1, False) soft_panel(sub, (w - 9, 0, w - 1, 28), BLUE, BLUE_D, 2, 1, False) soft_panel(sub, (0, h - 9, 28, h - 1), BLUE, BLUE_D, 2, 1, False) soft_panel(sub, (0, h - 29, 8, h - 1), BLUE, BLUE_D, 2, 1, False) soft_panel(sub, (w - 29, h - 9, w - 1, h - 1), BLUE, BLUE_D, 2, 1, False) soft_panel(sub, (w - 9, h - 29, w - 1, h - 1), BLUE, BLUE_D, 2, 1, False) place(img, "qr_scan_frame", 960, 1328, 128, 128, qr) biz_row1 = [ ("ico_server", 1200, "server"), ("ico_user", 1272, "user"), ("ico_chat", 1344, "chat"), ("ico_mail", 1416, "mail"), ("ico_settings", 1488, "settings"), ("ico_sound_on", 1560, "sound_on"), ("ico_sound_off", 1632, "sound_off"), ("ico_scan", 1704, "scan"), ("ico_refresh", 1776, "refresh"), ("ico_download", 1848, "download"), ("ico_upload", 1920, "upload"), ] biz_row2 = [ ("ico_bag", 1200, "bag"), ("ico_item", 1272, "item"), ("ico_sword", 1344, "sword"), ("ico_shield", 1416, "shield"), ("ico_map", 1488, "map"), ("ico_star", 1560, "star"), ("ico_heart", 1632, "heart"), ("ico_diamond", 1704, "diamond"), ("ico_key", 1776, "key"), ("ico_clock", 1848, "clock"), ("ico_calendar", 1920, "calendar"), ] icon_colors = { "server": BLUE, "user": ORANGE, "chat": GREEN, "mail": BLUE_L, "settings": GREY_D, "sound_on": BLUE, "sound_off": GREY, "scan": BLUE_D, "refresh": GREEN, "download": BLUE, "upload": ORANGE, "bag": ORANGE, "item": YELLOW, "sword": GREY_D, "shield": BLUE, "map": GREEN_L, "star": YELLOW, "heart": PINK, "diamond": BLUE_L, "key": ORANGE, "clock": BLUE, "calendar": PINK, } def make_biz(k): col = icon_colors.get(k, TEXT_BRN) def _p(sub, d, w, h): soft_panel(sub, (0, 0, w - 1, h - 1), shade(col, 70), shade(col, -30), 14, 2) draw_icon_simple(d, k, w // 2, h // 2, 18, WHITE, shade(col, -50)) return _p for name, x, kind in biz_row1: place(img, name, x, 1184, 64, 64, make_biz(kind)) for name, x, kind in biz_row2: place(img, name, x, 1256, 64, 64, make_biz(kind)) # ---------- reserved placeholders (dashed boxes, labeled) ---------- def reserved(label): def _p(sub, d, w, h): # translucent dashed-style placeholder soft_panel(sub, (0, 0, w - 1, h - 1), (245, 232, 210, 80), (168, 132, 92, 120), 20, 3, False) d.text((w // 2, h // 2), label, font=font_md, fill=(168, 132, 92, 180), anchor="mm") return _p place(img, "reserved_large_0", 8, 1464, 512, 256, reserved("Reserved")) place(img, "reserved_large_1", 528, 1464, 512, 256, reserved("Reserved")) def icon_grid(sub, d, w, h): soft_panel(sub, (0, 0, w - 1, h - 1), (245, 232, 210, 60), (168, 132, 92, 100), 16, 2, False) # 64x64 grid hint cols = w // 72 rows = h // 72 for r in range(rows): for c in range(cols): x0 = 8 + c * 72 y0 = 8 + r * 72 if x0 + 64 <= w - 4 and y0 + 64 <= h - 4: soft_panel(sub, (x0, y0, x0 + 63, y0 + 63), (255, 255, 255, 40), (168, 132, 92, 80), 10, 1, False) place(img, "reserved_icon_grid", 1200, 1336, 784, 192, icon_grid) # footer label strip (non-slice decoration outside reserved? skip — keep clean for Unity) img.save(OUT, "PNG") print(f"Wrote {OUT} ({SIZE}x{SIZE})") if __name__ == "__main__": main()