feat(local-tool): macOS DMG 美化(create-dmg 背景圖 + Applications 捷徑)
需求:Mac 端 installer 體驗類比 Windows .exe — 進 DMG 就看到漂亮的視窗
背景 + 拖到 Applications 的視覺引導。
實作:
- installer/macos/ 新資料夾
- make-dmg-background.py:動態生成 640×400 深色背景,配色對齊 Wails
控制台 splash(#111827→#0B0F19 漸層 + #38BDF8 accent)
- background.png + background@2x.png(1x + 2x Retina)
- Makefile dmg 拆三 target:
- dmg:auto-detect,有 create-dmg 走 fancy,沒有 fallback plain(CI 無痛)
- dmg-fancy:強制美化版(需 `brew install create-dmg`)
- dmg-plain:原 hdiutil UDZO(保留為 fallback)
- Windows / Linux 流程零改動
驗證:
- `make dmg-fancy` 產出 157MB DMG,mount 後內容:app + Applications 捷徑
+ .background/background.png + .DS_Store(視窗樣式)
- `hdiutil verify` 通過
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d0b33f8c71
commit
c1d2e2ddaa
@ -520,15 +520,48 @@ wails-linux: payload-linux ## ⚠️ 必須在 Linux runner 上執行:wails bu
|
|||||||
@du -sh visiona-local/build/bin/visiona-local
|
@du -sh visiona-local/build/bin/visiona-local
|
||||||
|
|
||||||
# ── 安裝檔打包 ─────────────────────────────────────────────────────
|
# ── 安裝檔打包 ─────────────────────────────────────────────────────
|
||||||
dmg: wails-macos ## hdiutil UDZO → dist/visiona-local.dmg
|
dmg: wails-macos ## 美化 DMG(create-dmg 有裝)或 plain DMG(fallback)→ dist/visiona-local.dmg
|
||||||
mkdir -p $(DIST)
|
@mkdir -p $(DIST)
|
||||||
|
@rm -f $(DIST)/visiona-local.dmg
|
||||||
|
@if command -v create-dmg > /dev/null 2>&1; then \
|
||||||
|
$(MAKE) --no-print-directory dmg-fancy; \
|
||||||
|
else \
|
||||||
|
echo "⚠️ create-dmg 未安裝,使用 plain DMG(hdiutil UDZO)"; \
|
||||||
|
echo " 想要美化版本請執行:brew install create-dmg"; \
|
||||||
|
$(MAKE) --no-print-directory dmg-plain; \
|
||||||
|
fi
|
||||||
|
@du -sh $(DIST)/visiona-local.dmg
|
||||||
|
@file $(DIST)/visiona-local.dmg
|
||||||
|
|
||||||
|
dmg-plain: ## hdiutil UDZO → dist/visiona-local.dmg(無背景圖,CI / fallback 用)
|
||||||
|
@mkdir -p $(DIST)
|
||||||
rm -f $(DIST)/visiona-local.dmg
|
rm -f $(DIST)/visiona-local.dmg
|
||||||
hdiutil create -volname "visionA-local" \
|
hdiutil create -volname "visionA-local" \
|
||||||
-srcfolder visiona-local/build/bin/visiona-local.app \
|
-srcfolder visiona-local/build/bin/visiona-local.app \
|
||||||
-ov -format UDZO \
|
-ov -format UDZO \
|
||||||
$(DIST)/visiona-local.dmg
|
$(DIST)/visiona-local.dmg
|
||||||
@du -sh $(DIST)/visiona-local.dmg
|
|
||||||
@file $(DIST)/visiona-local.dmg
|
dmg-fancy: ## create-dmg 美化版 → dist/visiona-local.dmg(需 brew install create-dmg)
|
||||||
|
@if [ ! -d visiona-local/build/bin/visiona-local.app ]; then \
|
||||||
|
echo "❌ visiona-local/build/bin/visiona-local.app 不存在,請先跑 make wails-macos"; exit 1; \
|
||||||
|
fi
|
||||||
|
@if ! command -v create-dmg > /dev/null 2>&1; then \
|
||||||
|
echo "❌ create-dmg 未安裝,請執行:brew install create-dmg"; exit 1; \
|
||||||
|
fi
|
||||||
|
@mkdir -p $(DIST)
|
||||||
|
rm -f $(DIST)/visiona-local.dmg
|
||||||
|
create-dmg \
|
||||||
|
--volname "visionA-local" \
|
||||||
|
--background installer/macos/background.png \
|
||||||
|
--window-pos 200 120 \
|
||||||
|
--window-size 640 400 \
|
||||||
|
--icon-size 128 \
|
||||||
|
--icon "visiona-local.app" 180 200 \
|
||||||
|
--app-drop-link 460 200 \
|
||||||
|
--hide-extension "visiona-local.app" \
|
||||||
|
--no-internet-enable \
|
||||||
|
$(DIST)/visiona-local.dmg \
|
||||||
|
visiona-local/build/bin/visiona-local.app
|
||||||
|
|
||||||
exe-only: ## 只跑 iscc 打包 installer(前置產物必須已存在),不重 build wails/payload
|
exe-only: ## 只跑 iscc 打包 installer(前置產物必須已存在),不重 build wails/payload
|
||||||
@if [ ! -f visiona-local/build/bin/visiona-local.exe ]; then \
|
@if [ ! -f visiona-local/build/bin/visiona-local.exe ]; then \
|
||||||
|
|||||||
42
local-tool/installer/macos/README.md
Normal file
42
local-tool/installer/macos/README.md
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# macOS DMG 美化資源
|
||||||
|
|
||||||
|
## 檔案
|
||||||
|
|
||||||
|
| 檔案 | 用途 |
|
||||||
|
|------|------|
|
||||||
|
| `make-dmg-background.py` | 生成背景圖的 Python script(需 Pillow) |
|
||||||
|
| `background.png` | 640×400 DMG 背景圖(1x) |
|
||||||
|
| `background@2x.png` | 1280×800 DMG 背景圖(Retina,create-dmg 自動挑用) |
|
||||||
|
|
||||||
|
## 使用
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 一次性安裝
|
||||||
|
brew install create-dmg
|
||||||
|
|
||||||
|
# Build 美化 DMG
|
||||||
|
make dmg
|
||||||
|
```
|
||||||
|
|
||||||
|
`make dmg` 會自動偵測 `create-dmg`:
|
||||||
|
- 有裝 → 產出美化版(深色背景 + 拖曳示意)
|
||||||
|
- 沒裝 → fallback 到 `make dmg-plain`(原本的 hdiutil UDZO,CI 友善)
|
||||||
|
|
||||||
|
可直接指定 target:
|
||||||
|
- `make dmg-fancy` — 強制美化版(`create-dmg` 未裝會報錯)
|
||||||
|
- `make dmg-plain` — 強制 plain 版
|
||||||
|
|
||||||
|
## 重新生成背景圖
|
||||||
|
|
||||||
|
改配色或文案時:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 installer/macos/make-dmg-background.py
|
||||||
|
```
|
||||||
|
|
||||||
|
會同時輸出 `background.png` 與 `background@2x.png`。
|
||||||
|
|
||||||
|
## 設計對齊
|
||||||
|
|
||||||
|
背景圖配色對齊 Wails 控制台深色 splash(`#111827` → `#0B0F19` 漸層 + `#38BDF8` accent)。
|
||||||
|
左側 app icon 位於 (180, 200),右側 Applications 捷徑位於 (460, 200),箭頭在中間。
|
||||||
BIN
local-tool/installer/macos/background.png
Normal file
BIN
local-tool/installer/macos/background.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.8 KiB |
BIN
local-tool/installer/macos/background@2x.png
Normal file
BIN
local-tool/installer/macos/background@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
85
local-tool/installer/macos/make-dmg-background.py
Normal file
85
local-tool/installer/macos/make-dmg-background.py
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
生成 DMG 美化背景圖(640×400),對齊 Wails 控制台深色 splash 風格。
|
||||||
|
|
||||||
|
用法:
|
||||||
|
python3 installer/macos/make-dmg-background.py
|
||||||
|
|
||||||
|
輸出:
|
||||||
|
installer/macos/background.png (1x, 640×400)
|
||||||
|
installer/macos/background@2x.png (2x, 1280×800 Retina)
|
||||||
|
|
||||||
|
create-dmg 會自動挑 @2x 版本用於 Retina 螢幕。
|
||||||
|
"""
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
|
||||||
|
OUT_DIR = Path(__file__).resolve().parent
|
||||||
|
W, H = 640, 400
|
||||||
|
|
||||||
|
BG_TOP = (17, 24, 39)
|
||||||
|
BG_BOTTOM = (11, 15, 25)
|
||||||
|
TEXT = (229, 231, 235)
|
||||||
|
MUTED = (148, 163, 184)
|
||||||
|
ACCENT = (56, 189, 248)
|
||||||
|
|
||||||
|
|
||||||
|
def make(scale: int, out_path: Path) -> None:
|
||||||
|
w, h = W * scale, H * scale
|
||||||
|
img = Image.new("RGB", (w, h), BG_TOP)
|
||||||
|
px = img.load()
|
||||||
|
for y in range(h):
|
||||||
|
t = y / (h - 1)
|
||||||
|
r = int(BG_TOP[0] + (BG_BOTTOM[0] - BG_TOP[0]) * t)
|
||||||
|
g = int(BG_TOP[1] + (BG_BOTTOM[1] - BG_TOP[1]) * t)
|
||||||
|
b = int(BG_TOP[2] + (BG_BOTTOM[2] - BG_TOP[2]) * t)
|
||||||
|
for x in range(w):
|
||||||
|
px[x, y] = (r, g, b)
|
||||||
|
|
||||||
|
draw = ImageDraw.Draw(img)
|
||||||
|
|
||||||
|
try:
|
||||||
|
font_title = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", 22 * scale)
|
||||||
|
font_hint = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", 14 * scale)
|
||||||
|
except OSError:
|
||||||
|
font_title = ImageFont.load_default()
|
||||||
|
font_hint = ImageFont.load_default()
|
||||||
|
|
||||||
|
title = "Drag visionA-local to Applications"
|
||||||
|
tw = draw.textlength(title, font=font_title)
|
||||||
|
draw.text(((w - tw) / 2, 28 * scale), title, fill=TEXT, font=font_title)
|
||||||
|
|
||||||
|
hint = "拖曳圖示到右邊的 Applications 即可安裝"
|
||||||
|
hw = draw.textlength(hint, font=font_hint)
|
||||||
|
draw.text(((w - hw) / 2, 60 * scale), hint, fill=MUTED, font=font_hint)
|
||||||
|
|
||||||
|
# 箭頭位置:左右兩個 icon 約在 y=230,中心。icon 本身 128×128,由 create-dmg 擺。
|
||||||
|
# create-dmg 預設 app icon x=180, Applications x=460(y=200)。箭頭畫在中間 240-400 區段。
|
||||||
|
arrow_y = 200 * scale
|
||||||
|
arrow_x1 = 260 * scale
|
||||||
|
arrow_x2 = 380 * scale
|
||||||
|
line_w = 3 * scale
|
||||||
|
draw.line([(arrow_x1, arrow_y), (arrow_x2, arrow_y)], fill=ACCENT, width=line_w)
|
||||||
|
# 箭頭頭
|
||||||
|
head = 12 * scale
|
||||||
|
draw.polygon(
|
||||||
|
[
|
||||||
|
(arrow_x2, arrow_y),
|
||||||
|
(arrow_x2 - head, arrow_y - head // 2 - 2 * scale),
|
||||||
|
(arrow_x2 - head, arrow_y + head // 2 + 2 * scale),
|
||||||
|
],
|
||||||
|
fill=ACCENT,
|
||||||
|
)
|
||||||
|
|
||||||
|
img.save(out_path, "PNG", optimize=True)
|
||||||
|
print(f"wrote {out_path} ({out_path.stat().st_size // 1024} KB)")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
make(1, OUT_DIR / "background.png")
|
||||||
|
make(2, OUT_DIR / "background@2x.png")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Loading…
x
Reference in New Issue
Block a user