Skip to content

TortoiseUI: 亀を任意の画像で描けるようにする - #42

Merged
temoki merged 1 commit into
mainfrom
tortoise-sprite-image
Aug 3, 2026
Merged

TortoiseUI: 亀を任意の画像で描けるようにする#42
temoki merged 1 commit into
mainfrom
tortoise-sprite-image

Conversation

@temoki

@temoki temoki commented Aug 3, 2026

Copy link
Copy Markdown
Owner

概要

TortoiseUITortoiseSprite.tortoiseSprite(_:) environment modifier を追加し、亀を組み込みの緑の三角形ではなく任意の画像で描けるようにします。

TortoiseCanvas(🐢)
    .tortoiseSprite(.image(Image("Turtle"), size: CGSize(width: 40, height: 40)))

デフォルトは従来どおり .triangle で、その描画結果は一切変わりません(既存の canvas ゴールデン画像が無変更で通ることで確認済み)。

設計上の判断

  • スプライトは \.tortoiseViewport と同様に environment 経由の表示側の設定であり、TortoiseCommand ではありません。したがってシリアライズされるコマンドストリームには入らず、TortoiseSVG にも影響しません(SVG 出力はもともと亀を描いていません)。
  • 画像は亀の位置を中心に配置し、画像の上辺が heading の向きになるよう回転します(heading 0 = 北 = 無回転)。透過はそのまま活きます。
  • sizeバウンディングボックス(ビューポート倍率1でのポイント数)として扱い、アスペクト比を保って内側にフィットさせます(SwiftUI の scaledToFit と同じ考え方)。その上で三角形と同様にビューポート倍率で拡縮し、0.5×〜2× にクランプされます。
  • ViewportMode.autoFit の端の inset を、三角形固定のサイズではなくスプライトの半対角長TortoiseSprite.halfExtent)基準に変更しました。これにより大きなスプライトでもビュー端で切れず、どの heading でも成立します。スプライトを描くのは AnimationLayer だけですが、2つのレイヤーは同一の transform を導出する必要があるため、両方が environment 値を読んでいます。
  • UIImage / NSImageImage(uiImage:) / Image(nsImage:) で渡してもらう形にし、プラットフォーム分岐を API に持ち込んでいません。size はポイント指定なので、Asset Catalog の @2x / @3x は自動的に効きます。

関連 issue

なし(直接の機能要望)

チェックリスト

  • swift test がローカルで通る(120件)

  • xcrun swift-format lint --recursive --strict Sources Tests が通る

  • 新しい挙動に対するテストを追加

    TortoiseSpriteTests を新規追加: halfExtentautoFit の inset、Equatable の単体テストに加え、macOS のスナップショット2件(画像が三角形を置き換えて heading 方向に回転すること/非正方形の画像が引き伸ばされずフィットすること)。スプライト画像は ImageRenderer でコード内生成しているため、テストリソースは増えていません。

  • CHANGELOG.md を更新

  • ゴールデン画像: デフォルトの三角形の描画結果は変わらないため、既存ゴールデンの再録は不要でした。新規追加した2件は目視で確認済みです。

README、TortoiseUI の DocC 記事、CLAUDE.md も更新し、TortoiseCanvas.swift#Preview("Custom Sprite") を追加しています。

Add `TortoiseSprite` and the `.tortoiseSprite(_:)` environment modifier, so
the tortoise can be drawn as the user's own image instead of the built-in
green triangle:

    TortoiseCanvas(🐢)
        .tortoiseSprite(.image(Image("Turtle"), size: CGSize(width: 40, height: 40)))

The sprite is a view-side setting, not a `TortoiseCommand`, so it never
enters the serialized stream and TortoiseSVG is unaffected.

The image is centered on the tortoise's position and rotated so its top edge
faces the heading, with transparency preserved. `size` is a bounding box in
points at viewport scale 1: the image is scaled to fit inside it with its
aspect ratio preserved, then scales with the viewport exactly like the
triangle (clamped to 0.5x-2x).

`ViewportMode.autoFit`'s edge inset now derives from the sprite's
half-diagonal instead of a triangle-sized constant, so a large sprite never
clips at the view edge. Both canvas layers read the environment value —
only `AnimationLayer` draws the sprite, but the two layers must derive the
identical transform.

The default is `.triangle` and its rendering is unchanged, which the
existing canvas goldens confirm.
@temoki
temoki merged commit 57f67ec into main Aug 3, 2026
3 of 4 checks passed
@temoki
temoki deleted the tortoise-sprite-image branch August 3, 2026 23:57
@temoki temoki changed the title Allow drawing the tortoise as a custom image (TortoiseUI) TortoiseUI: 亀を任意の画像で描けるようにする Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant