ChatGPT,アプリ開発,ノート,生成AI

考えたことメモ > App InventorからiPhoneアプリを作るには?

App InventorからPythonへどうやって移行するのか?」でChatGPT-4oがApp Inventorのaiaファイルを直接読み込み、アプリの要件定義書を書いてそのまま動作するPythonコードを書けました。

ここではChatGPT-5を使ってApp Inventorのaiaファイルを直接読み込み、アプリの要件定義書を書いてiPhone用のSwiftUIコードを書いてみます。

出発点のApp Inventorアプリは「オンデマンドアカデミー」で紹介されている カラフルお絵かきアプリです。このアプリは、基本的なUIの構成やイベント処理の理解に適しています。

aiaファイルの読み込みと解析

要件定義書作成

SwiftUIプログラミング

動作確認

サマリー

ChatGPT-5はApp Inventorのaiaファイルを直接読み込み、アプリの要件定義書を書いてそのまま動作するSwiftUIコードを書けました。

aiaファイルの読み込みと解析

プロンプト

あなたはApp Inventor 2の内部構造に精通したプロのソフトウェアエンジニアです。このaiaファイルを完全に解析してください。

返答

解析しました!まず要点をざっと👇

画面は Canvas(キャンバス)+3本の水平配置。上段に色(黒/青/グリーン/赤)、中段に太さ(細/中/※太の候補)、下段に「画面消去」「消しゴム」ボタン。

ブロックは 126ノード。主な構成は変数取得/設定、イベント、プロシージャ定義、Canvas 操作(DrawLine / Clear)。

定義プロシージャ:色ボタンリセット / 色ボタン選択 / 太さボタンリセット / 太さボタン選択 / 消しゴムボタン選択。

イベント:Screen1.Initialize, Canvas.Dragged, (ボタン)Click。
初期化で「選択中の色」「ペン太さ」を渡してプロシージャ呼び出し→UI状態と PaintColor / LineWidth を確定。
Dragged では prevX, prevY -> currentX, currentY へ DrawLine で描画。
「画面消去」は Canvas.Clear、消しゴムは白系で描く実装。

プロジェクト設定:sizing=Responsive、テーマ AppTheme.Light.DarkActionBar、位置情報未使用、拡張/アセットなし、バージョン 1.0 (code 1)。

フルの技術レポート(コンポーネント階層、ボタン属性表、イベント/メソッド一覧、推定挙動、改善提案まで)を用意しました。
# DoodlePlus(.aia)完全解析レポート

このレポートは MIT App Inventor 2 プロジェクトの .aia を展開し、画面(.scm)とブロック(.bky)、プロジェクト設定(project.properties)を静的解析した内容です。

## 1) プロジェクト設定(youngandroidproject/project.properties)

```
#
#Sat Apr 12 01:36:28 UTC 2025
sizing=Responsive
color.primary.dark=&HFF303F9F
color.primary=&HFF3F51B5
color.accent=&HFFFF4081
aname=DoodlePlus
defaultfilescope=App
main=appinventor.*****.DoodlePlus.Screen1
source=../src
actionbar=True
useslocation=False
assets=../assets
build=../build
name=DoodlePlus
showlistsasjson=True
theme=AppTheme.Light.DarkActionBar
versioncode=1
versionname=1.0
```

主な項目:

- アプリ名 (aname / name): **DoodlePlus / お絵かきアプリ+**
- メイン画面: **Screen1**
- テーマ: **showlistsasjson=True**
- デフォルトファイルスコープ: **main=appinventor.****.DoodlePlus.Screen1**
- ActionBar: **True**
- 位置情報使用 (useslocation): **False**

## 2) 画面レイアウト(Screen1.scm)

- 画面タイトル: **お絵かきアプリ+**
- コンポーネント数: **12**

### コンポーネント階層

- `キャンバス1` : Canvas (親: None)
- `水平配置1` : HorizontalArrangement (親: None)
  - `黒` : Button (親: 水平配置1)
  - `青` : Button (親: 水平配置1)
  - `グリーン` : Button (親: 水平配置1)
  - `赤` : Button (親: 水平配置1)
- `水平配置2` : HorizontalArrangement (親: None)
  - `細` : Button (親: 水平配置2)
  - `中` : Button (親: 水平配置2)
- `水平配置3` : HorizontalArrangement (親: None)
  - `画面消去` : Button (親: 水平配置3)
  - `消しゴム` : Button (親: 水平配置3)

### カラーボタン・太さボタン等の属性

| 親配置 | ボタン名 | 表示テキスト | 背景色 | フォントサイズ | 幅 |
|---|---|---|---|---|---|
| 水平配置1 | 黒 |  | &HFF000000 | 10 | -1025 |
| 水平配置1 | 青 |  | &HFF0000FF | 10 | -1025 |
| 水平配置1 | グリーン |  | &HFF00FF00 | 10 | -1025 |
| 水平配置1 | 赤 |  | &HFFFF0000 | 10 | -1025 |
| 水平配置2 | 細 | 細 |  |  |  |
| 水平配置2 | 中 | 中 |  |  |  |
| 水平配置3 | 画面消去 | 画面消去 |  |  |  |
| 水平配置3 | 消しゴム | 消しゴム |  |  |  |

## 3) ブロック(Screen1.bky)

- 総ブロック数(node): **126**
### ブロック種別の出現数(上位)
- lexical_variable_get: 34
- component_component_block: 14
- component_set_get: 14
- global_declaration: 7
- procedures_callnoreturn: 7
- procedures_defnoreturn: 5
- logic_false: 4
- controls_if: 4
- logic_compare: 4
- lexical_variable_set: 4
- lists_create_with: 3
- color_black: 3
- component_event: 3
- text: 2
- math_number: 2

### イベントハンドラ
- Screen1 : Form.Initialize
- (未指定) : Button.Click
- キャンバス1 : Canvas.Dragged

### 定義済みプロシージャ
- 色ボタンリセット
- 色ボタン選択
- 太さボタンリセット
- 太さボタン選択
- 消しゴムボタン選択

(推定機能)
- **色ボタンリセット / 色ボタン選択**: 色選択UIの状態管理(選択記号の付与・他色のリセット、および `Canvas.PaintColor` の更新)
- **太さボタンリセット / 太さボタン選択**: ペンの太さUIの状態管理(`Canvas.LineWidth` の更新)
- **消しゴムボタン選択**: 消しゴムモード(背景色=白 or 透明、`LineWidth` 固定化等)への切替

### 画面初期化時の呼び出し(Screen1.Initialize)
- Call 色ボタン選択 with args: [('var', 'global 選択色ボタン')]
- Call 太さボタン選択 with args: [('var', 'global ペン太さ')]

### メソッド利用(コンポーネント操作)

- キャンバス1 (Canvas).**Clear**
- キャンバス1 (Canvas).**DrawLine**

※ 少なくとも `Canvas.DrawLine`, `Canvas.Clear` を使用しており、ドラッグで線分を描画し、[画面消去] でキャンバスをクリアする仕様です。

## 4) 振る舞いの要約(静的解析からの推定)

- 画面上部に **Canvas**、下部に3行の **HorizontalArrangement**(色選択、太さ選択、ユーティリティ)が並ぶ構成。
- 色ボタン(`黒` / `青` / `グリーン` / `赤`)で `Canvas.PaintColor` を切替。選択中ボタンにはチェック記号(例: ✔︎)を付ける実装。
- 太さボタン(`細` / `中` / (恐らく `太`))で `Canvas.LineWidth` を切替。
- `画面消去` ボタンで `Canvas.Clear`。
- `消しゴム` ボタンで描画色を背景色(白)相当にして消す振る舞い。
- `Canvas.Dragged` では `prevX, prevY` → `currentX, currentY` に対して **DrawLine** する一般的なお絵かきロジック。

## 5) 拡張機能・アセット・権限

- 拡張(.aix): **未使用**(拡張の痕跡なし)
- アセット(/assets): **未検出**(aiaに含まれない)
- パーミッション: project.properties 上は **位置情報未使用**。その他の特別権限の記載なし。

## 6) 改善できるポイント(提案)

- **ボタンのアクセシビリティ**: 色名テキストの有無に揺れがあるため、全ボタンで `Text` を明示し `ContentDescription` を付けると支援技術に優しいです。
- **消しゴムモードの視覚化**: 現状のチェック記号流用だけでなく、トグル表示やボタン色の反転などで状態を明確にできます。
- **キャンバスサイズ**: `Height/Width=-2`(Fill Parent)使用だが、端末回転時の計測タイミングに注意。必要なら `sizing=Responsive` に合わせて初回レイアウト後に線幅を再設定。
- **Undo/Redo**: 線分の履歴リストを持ち、`Canvas.Clear` 前にバックアップすれば Undo が実装可能。
- **保存/共有**: `Canvas.SaveAs` による画像保存、`Sharing` コンポーネントによる共有の追加を検討。

要件定義書作成

プロンプト

このアプリの要件定義書をMarkdownで書いて

返答

線の太さに「太」が追加されていますが、よしとしましょう。

# DoodlePlus アプリ要件定義書

## 1. 概要

DoodlePlus は、Android スマートフォン上で動作するシンプルなお絵かきアプリである。ユーザは指でキャンバスに線を描画し、色や線の太さを切り替えたり、消しゴムで描いた線を消去したりできる。さらに、キャンバス全体を一括でクリアする機能を提供する。

## 2. 対象ユーザー

- 幼児から大人まで幅広い年齢層
- 落書きやメモを素早く行いたいユーザー
- 学習教材やプレゼンの補助に使いたいユーザー

## 3. 利用目的

- 指先で自由に絵や文字を描く
- 色や太さを変えた線で描画
- 消しゴム機能で部分修正
- 全消去で新しいキャンバスにリセット

## 4. 機能要件

### 4.1 キャンバス機能
- 指でドラッグした座標間に線を描画
- 線の色、太さを設定可能
- 全体を消去する機能(Canvas.Clear)

### 4.2 色選択機能
- 黒 / 青 / 緑 / 赤 の4色を選択可能
- 選択中の色を視覚的に強調表示(チェック記号)

### 4.3 線の太さ選択機能
- 細 / 中 / 太 の3段階から選択可能
- 選択中の太さを視覚的に強調表示

### 4.4 消しゴム機能
- 描画色を背景色(白)に変更して消去
- 消しゴム使用中は太さ固定(中程度)

### 4.5 全消去機能
- ボタン操作でキャンバス全体をクリア

## 5. 非機能要件

- **操作性**: タップ/ドラッグで直感的に利用可能
- **応答速度**: 線描画・消去操作はリアルタイムで反映
- **アクセシビリティ**: ボタンにテキストラベルを付与し、視認性を確保
- **互換性**: Android 5.0 以上で動作

## 6. UI要件

- 上部: 描画用キャンバス(画面の大部分を占有)
- 下部: 3列の水平配置
  - 1行目: 色選択ボタン(黒/青/緑/赤)
  - 2行目: 太さ選択ボタン(細/中/太)
  - 3行目: [消しゴム] ボタン, [画面消去] ボタン

## 7. 将来的な拡張要件(提案)

- 描画の Undo / Redo 機能
- 画像保存および共有機能
- 消しゴムモードを視覚的に分かりやすくするトグル表示
- 色や太さのカスタム選択(カラーピッカー/スライダー導入)

## 8. 制約条件

- MIT App Inventor 2 で開発
- 外部拡張(.aix)は使用しない
- インターネットや位置情報の権限は不要

## 9. 品質保証

- 動作確認: Android 実機でのテスト
- 正常系テスト: 色/太さ選択、描画、消去操作が正しく動作すること
- 異常系テスト: 高速なタッチ操作や多指操作でも動作が破綻しないこと

SwiftUIプログラミング

プロンプト

この要件定義書に基づいてSwiftUIのコードを書いて。初心者にも何をしているかわかるように、また、App Inventorアプリとの対応関係がわかるように、詳しいコメントをつけて。

返答

了解!要件定義書の機能に対応する SwiftUI 実装を、App Inventor(AI2)のコンポーネント/ブロックとの対応関係が分かるように、丁寧なコメント付きでまとめました。
以下の単一ファイル(ContentView.swift)を Xcode の SwiftUI プロジェクトに追加すれば、そのまま動作します(iOS 15+ を想定)。

//
//  ContentView.swift
//
//  DoodlePlus (SwiftUI port)
//  - MIT App Inventor 2 版の「Canvas + 色選択 + 太さ選択 + 消しゴム + 全消去」をSwiftUIで再現
//  - 初心者向けに、できるだけ丁寧にコメントしています。
//  - App Inventor のコンポーネント/ブロック対応をコード中に明記。
//  - 透明消去(アルファ抜き)ではなく「白で上書きする」方式の消しゴムで、AI2の挙動に近づけています。
//    (※本格的な透明消去をしたい場合は Core Graphics のブレンドモード destinationOut などの応用が必要)
//

import SwiftUI

// MARK: - モデル定義(AI2で言うところの「変数」や「状態」)
//
// App Inventor 対応:
// - PaintColor(Canvasの描画色)→ selectedColor
// - LineWidth   (線の太さ)    → selectedWidth
// - 消しゴムモード               → isEraser
// - 描いた線の履歴               → strokes(各ストロークは points の配列)
//

/// 線1本(ストローク)を表すモデル
struct Stroke: Identifiable {
    let id = UUID()
    var points: [CGPoint]     // 線を構成する点列(指でなぞった座標)
    var color: Color          // 線の色(消しゴムなら Color.white)
    var lineWidth: CGFloat    // 線の太さ
}

/// アプリ全体の状態
final class DrawingState: ObservableObject {
    // 現在の選択色(AI2: Canvas.PaintColor)
    @Published var selectedColor: Color = .black
    // 現在の線の太さ(AI2: Canvas.LineWidth)
    @Published var selectedWidth: CGFloat = 6
    // 消しゴムモード(AI2: 「消しゴムボタン選択」プロシージャで true/false を切替)
    @Published var isEraser: Bool = false

    // これまでに描いた全ストローク(AI2: DrawLine を積み重ねるイメージ)
    @Published var strokes: [Stroke] = []
    // ドラッグ中の「現在のストローク」(指を離したら strokes に確定追加)
    @Published var currentStroke: Stroke? = nil

    // 画面全消去(AI2: Canvas.Clear)
    func clear() {
        strokes.removeAll()
        currentStroke = nil
    }
}

// MARK: - SwiftUI 版 Canvas(AI2: Canvas コンポーネントに相当)
//
// - SwiftUI の Canvas/Path を使って、strokes と currentStroke を描画します。
// - App Inventor の Canvas.DrawLine(block) 相当の処理は、points を連結して Path にしています。
//
struct DrawingCanvas: View {
    @ObservedObject var state: DrawingState

    var body: some View {
        GeometryReader { geo in
            ZStack {
                // 背景は白(AI2: Canvas の BackgroundColor に相当)
                Color.white
                    .ignoresSafeArea()

                // これまでに描いたストロークを全部描画
                ForEach(state.strokes) { stroke in
                    Path { path in
                        guard let first = stroke.points.first else { return }
                        path.move(to: first)
                        for p in stroke.points.dropFirst() {
                            path.addLine(to: p)
                        }
                    }
                    .stroke(stroke.color, lineWidth: stroke.lineWidth)
                }

                // ドラッグ中のストロークもプレビュー表示
                if let stroke = state.currentStroke {
                    Path { path in
                        guard let first = stroke.points.first else { return }
                        path.move(to: first)
                        for p in stroke.points.dropFirst() {
                            path.addLine(to: p)
                        }
                    }
                    .stroke(stroke.color, lineWidth: stroke.lineWidth)
                }
            }
            // ジェスチャ:指でなぞる → 点を追加 → 指を離したら確定
            // App Inventor 対応:
            //   - Canvas.Dragged(prevX, prevY, currentX, currentY) で DrawLine しているのと同等
            .gesture(
                DragGesture(minimumDistance: 0)
                    .onChanged { value in
                        // ドラッグが始まったら currentStroke を作成(色/太さは現在の選択)
                        if state.currentStroke == nil {
                            let color = state.isEraser ? Color.white : state.selectedColor
                            state.currentStroke = Stroke(points: [value.location], color: color, lineWidth: state.selectedWidth)
                        } else {
                            // すでに作成済みなら座標を追加
                            state.currentStroke?.points.append(value.location)
                        }
                    }
                    .onEnded { value in
                        // 指を離したら確定(strokes に追加)し、currentStroke をクリア
                        if var stroke = state.currentStroke {
                            stroke.points.append(value.location)
                            state.strokes.append(stroke)
                        }
                        state.currentStroke = nil
                    }
            )
        }
    }
}

// MARK: - メインビュー(AI2: Screen1 に相当)
//
// 画面構成(要件/UI要件に基づく)
//  - 上部:描画用 Canvas
//  - 下部:3つの水平行(HStack)
//      1行目:色選択(黒/青/緑/赤)→ PaintColor 切替
//      2行目:太さ選択(細/中/太)→ LineWidth 切替
//      3行目:消しゴムトグル / 画面消去 → isEraser 切替 / Canvas.Clear
//
struct ContentView: View {
    @StateObject private var state = DrawingState()

    // 太さの候補(AI2: 「細/中/太」)
    private let widthOptions: [(label: String, value: CGFloat)] = [
        ("細", 4),
        ("中", 8),
        ("太", 14)
    ]

    // 色の候補(AI2: 「黒/青/緑/赤」)
    private let colorOptions: [(label: String, value: Color)] = [
        ("黒", .black),
        ("青", .blue),
        ("緑", .green),
        ("赤", .red)
    ]

    var body: some View {
        VStack(spacing: 0) {
            // 1) Canvas 領域(要件: 画面の大部分)
            DrawingCanvas(state: state)
                .overlay(alignment: .topLeading) {
                    // シンプルなタイトル(AI2: Screen1.Title)
                    Text("DoodlePlus")
                        .font(.headline)
                        .padding(8)
                        .background(.ultraThinMaterial)
                        .cornerRadius(8)
                        .padding(8)
                        .accessibilityHidden(true)
                }

            Divider()

            // 2) 下部パネル:3段構成
            VStack(spacing: 8) {
                // --- 行1: 色選択(AI2: 色ボタン群 + 「色ボタン選択/リセット」プロシージャに対応)---
                controlRow(title: "色", content: {
                    HStack {
                        ForEach(colorOptions, id: \.label) { opt in
                            Button {
                                // 色選択:PaintColor を更新、かつ消しゴムモードは OFF
                                state.selectedColor = opt.value
                                state.isEraser = false
                            } label: {
                                HStack(spacing: 6) {
                                    Circle()
                                        .fill(opt.value)
                                        .frame(width: 18, height: 18)
                                    Text(opt.label)
                                    // 選択中マーク(AI2: チェック記号の代替として表示)
                                    if !state.isEraser && state.selectedColor.description == opt.value.description {
                                        Image(systemName: "checkmark")
                                    }
                                }
                                .padding(.horizontal, 10)
                                .padding(.vertical, 8)
                                .background(
                                    // 選択中は控えめに強調
                                    (!state.isEraser && state.selectedColor.description == opt.value.description) ?
                                        Color.secondary.opacity(0.15) : Color.clear
                                )
                                .cornerRadius(10)
                            }
                            .accessibilityLabel(Text("色 \(opt.label)"))
                        }
                    }
                })

                // --- 行2: 太さ選択(AI2: 太さボタン群 + 「太さボタン選択/リセット」プロシージャに対応)---
                controlRow(title: "太さ", content: {
                    HStack {
                        ForEach(widthOptions, id: \.label) { opt in
                            Button {
                                state.selectedWidth = opt.value
                            } label: {
                                HStack(spacing: 8) {
                                    // 太さプレビュー
                                    Capsule()
                                        .frame(width: 28, height: max(4, opt.value / 1.6))
                                    Text(opt.label)
                                    if state.selectedWidth == opt.value {
                                        Image(systemName: "checkmark")
                                    }
                                }
                                .padding(.horizontal, 10)
                                .padding(.vertical, 8)
                                .background(
                                    (state.selectedWidth == opt.value) ?
                                        Color.secondary.opacity(0.15) : Color.clear
                                )
                                .cornerRadius(10)
                            }
                            .accessibilityLabel(Text("太さ \(opt.label)"))
                        }
                    }
                })

                // --- 行3: ユーティリティ(AI2: 消しゴム / 画面消去ボタン)---
                controlRow(title: "操作", content: {
                    HStack {
                        // 消しゴム(AI2: 消しゴムボタン選択 → isEraser = true、UI強調)
                        Button {
                            state.isEraser.toggle()
                        } label: {
                            Label("消しゴム", systemImage: state.isEraser ? "eraser.fill" : "eraser")
                                .padding(.horizontal, 12)
                                .padding(.vertical, 10)
                                .background(state.isEraser ? Color.secondary.opacity(0.2) : Color.clear)
                                .cornerRadius(10)
                        }
                        .accessibilityLabel(Text("消しゴム"))
                        .accessibilityHint(Text("オンにすると白い線で上書きして消せます"))

                        Spacer(minLength: 12)

                        // 画面消去(AI2: Canvas.Clear)
                        Button(role: .destructive) {
                            state.clear()
                        } label: {
                            Label("画面消去", systemImage: "trash")
                                .padding(.horizontal, 12)
                                .padding(.vertical, 10)
                                .background(Color.red.opacity(0.12))
                                .cornerRadius(10)
                        }
                        .accessibilityLabel(Text("画面消去"))
                        .accessibilityHint(Text("キャンバスをすべてクリアします"))
                    }
                })
            }
            .padding(12)
            .background(.ultraThinMaterial)
        }
        .preferredColorScheme(.light) // 背景白に合わせた見え方重視(任意)
    }

    // 下部各行のラッパー(タイトル+内容)
    private func controlRow<Content: View>(title: String, @ViewBuilder content: () -> Content) -> some View {
        VStack(alignment: .leading, spacing: 6) {
            Text(title)
                .font(.subheadline).bold()
                .accessibilityHidden(true)
            content()
        }
    }
}

// MARK: - プレビュー
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .previewDevice("iPhone 15")
    }
}
App Inventor(AI2)との対応表(抜粋)
App Inventor	SwiftUI 実装	補足
Screen1	ContentView	画面のメインビュー
Canvas	DrawingCanvas(GeometryReader + Path/ZStack)	背景白・ストローク配列を描画
Canvas.Dragged	DragGesture.onChanged / onEnded	ドラッグ座標を currentStroke.points に追加・確定
Canvas.DrawLine	Path.addLine(to:)	点列から線を生成
Canvas.Clear	state.clear()	strokes.removeAll()
色ボタン(黒/青/緑/赤)	colorOptions + ボタン群	押下で selectedColor セット&消しゴム解除
太さボタン(細/中/太)	widthOptions + ボタン群	押下で selectedWidth セット
消しゴム	state.isEraser.toggle()	消しゴムON時は Color.white で上書き
選択中のチェック表示	Image(systemName:"checkmark")	状態に応じて表示
使い方メモ(初心者向け)

ボタンで 色 と 太さ を選んで、上の白いキャンバスを指でなぞると線が描けます。

消しゴム を押すと白い線で上書きでき、描いた線を消す感覚になります(背景が白のため)。

画面消去 でキャンバスをまっさらな状態に戻せます。

透明消去(背景が透ける消しゴム)にしたい場合は、Canvas レイヤーを CALayer / Core Graphics で管理し、ブレンドモード(destinationOut 等)を使う設計に変更すれば実現できます。

必要なら、この SwiftUI 版に Undo/Redo や 画像保存/共有 を追加した発展版コードもすぐ出せます。どう拡張したいか教えてください!

動作確認

iPhoneアプリを作るにはMacで動くXcodeという開発環境が必要です。ここではmacOS 13.7.5上でXcode 15.2を使い、動作確認にはiOSシミュレータ(iOS 15.2)を使いました。

公開済みAI関連アプリチュートリアル:シルバー会員特典

ゴールド会員特典はAIが中心

初心者にはこちらも

アプリ開発,ノート

考えたことメモ > App Inventor nb202bでiPhoneアプリを作ってみた

2025年8月25日にMITで公開されたApp Inventor nb202bでベータではありますが、iOSアプリのビルド機能が追加されたのでうまく動くか実験してみました。

日本語化プロジェクトのサーバーではまだiOSアプリのビルド機能はサポートしていません。

https://appinventor.mit.edu/ai2/ReleaseNotes

試したアプリは以下の通りです。

自分のApp InventorアプリをiPhoneでビルドしてみたい人は会員専用コミュニティフォーラム:ご意見・ご要望コーナーから連絡ください。

準備

App InventorでiOSアプリをビルドするにはプロビジョニングプロファイルファイルを作る必要があり、そのためには年額$99のApple Developer Programへの加入が必要です。私たちはApple Developer Programに加入済みなので、https://ai2.appinventor.mit.edu/reference/other/build-ios-apps.html に従ってプロビジョニングプロファイルファイルを作りました。

四則演算電卓アプリ

動作状況

  • ビルド自体は正常終了。
  • デザインがまるで違う。
  • ボタンが動作しない。

公開済みAI関連アプリチュートリアル:シルバー会員特典

ゴールド会員特典はAIが中心

初心者にはこちらも

ChatGPT,アプリ開発,ノート,生成AI

考えたことメモ > App InventorからJavaScriptへどうやって移行するのか?

Google Gemini 2.5 Pro Deep Researchによれば、「一度App Inventorで作ったアプリをPythonで再現してみるというアプローチは、目的が明確なため非常に効果的な学習方法です」とのことでした。これは、AI時代における実践的なプログラミング学習の一例といえます。

Pythonは文法が比較的シンプルで、初心者にも扱いやすい言語です。AI開発、データ分析、Webサイト制作、作業の自動化など、幅広い分野に応用できる点が特徴です。

一方、JavaScriptPythonにならんで非常にポピュラーなプログラミング言語で、ウェブページで使われるのでウェブサイトを作るには必須と言えるプログラミング言語です。node.jsを使えばサーバーサイドの開発もできるので、ウェブアプリの開発にはこれ一つ知っていれば十分です。

App InventorからPythonへどうやって移行するのか?」でChatGPT-4oがApp Inventorのaiaファイルを直接読み込み、アプリの要件定義書を書いてそのまま動作するPythonコードを書けました。

ここではChatGPT-5を使ってApp Inventorのaiaファイルを直接読み込み、アプリの要件定義書を書いてJavaScriptコードを書いてみます。

出発点のApp Inventorアプリは「オンデマンドアカデミー」で紹介されている カラフルお絵かきアプリです。このアプリは、基本的なUIの構成やイベント処理の理解に適しています。

aiaファイルの読み込みと解析

要件定義書作成

JavaScriptプログラミング

動作確認

サマリー

ChatGPT-5はApp Inventorのaiaファイルを直接読み込み、アプリの要件定義書を書いてそのまま動作するJavaScriptコードを書けました。

aiaファイルの読み込みと解析

プロンプト

あなたはApp Inventor 2の内部構造に精通したプロのソフトウェアエンジニアです。このaiaファイルを完全に解析してください。

返答

了解です。アップロードいただいた **DoodlePlus.aia** を、中身(.scm / .bky / project.properties)まで読み解いて、構造・ロジック・設定を“丸裸”にしてご報告します。以下は App Inventor 2 の内部表現(JSON/Blockly XML)をベースにした、技術者向けの完全解析です。

# プロジェクト概要

* アプリ名: **DoodlePlus**(画面タイトル: **お絵かきアプリ+**)
* スクリーン数: **1(Screen1)**
* 作成/保存タイムスタンプ: **Sat Apr 12 01:36:28 UTC 2025**
* バージョン: **versionCode=1 / versionName=1.0**
* レイアウト: **sizing=Responsive**, **ActionBar=True**, **Theme=AppTheme.Light.DarkActionBar**
* 既定の権限/設定: **useslocation=False**, **defaultfilescope=App**, **showlistsasjson=True**
* 配色(プロジェクト設定)

  * Primary: `&HFF3F51B5`
  * Primary Dark: `&HFF303F9F`
  * Accent: `&HFFFF4081`

# 画面とUI階層(Screen1)

Screen1 直下に下記 12 コンポーネントが存在します。

* **キャンバス1**(Canvas): `Width=-2`, `Height=-2` → いずれも **Fill Parent**
* **水平配置1**(HorizontalArrangement)
  ├─ ボタン **黒**(BackgroundColor `#000000`, FontSize 10)
  ├─ ボタン **青**(`#0000FF`, FontSize 10)
  ├─ ボタン **グリーン**(`#00FF00`, FontSize 10)
  └─ ボタン **赤**(`#FF0000`, FontSize 10)
* **水平配置2**
  ├─ ボタン **細**(Text=「細」)
  └─ ボタン **中**(Text=「中」)
* **水平配置3**
  ├─ ボタン **画面消去**(Text=「画面消去」)
  └─ ボタン **消しゴム**(Text=「消しゴム」)

> 補足: Canvas は全面に描画。3 つの水平配置がパレット(色/太さ/アクション)になっています。

# グローバル変数(global\_declaration)

* **選択マーク** : 文字列 `"✔︎"`
* **選択色ボタン** : Button 参照(初期値 **黒**)
* **色ボタンリスト** : `[黒, 青, グリーン, 赤]`
* **ペン太さ** : Button 参照(初期値 **細**)
* **太さボタンリスト** : `[細, 中]`
* **太さリスト** : `[3.0, 5.0]`  ← 太さボタンの位置と対応
* **消しゴム** : `false`(ブール)

# 手続き(procedures)

1. **色ボタンリセット**

   * `色ボタンリスト` を全走査し、各ボタンの **Text を空文字**にする(✔︎マーク消去)。

2. **色ボタン選択(ボタン)**

   * 以前の **選択色ボタン** の **TextColor を黒**に戻す。
   * 引数 **ボタン** の **TextColor を白**にし、\*\*Text に「✔︎」\*\*を表示。
   * **選択色ボタン ← ボタン** に更新。
   * **キャンバス1.PaintColor ← 選択色ボタン.BackgroundColor** に設定。
   * (実装全体として)色選択時には **消しゴム ← false** に戻す挙動が組まれています。

3. **太さボタンリセット**

   * `太さボタンリスト` を全走査し、各ボタンの **TextColor を黒**に戻す。

4. **太さボタン選択(ボタン)**

   * **ボタン.TextColor ← 赤**(選択強調)。
   * **ペン太さ ← ボタン** に更新。
   * `太さボタンリスト` 内での **ペン太さ** の位置に基づき、`太さリスト` から対応値を取得し、
     **キャンバス1.LineWidth** に反映(`細→3.0px`, `中→5.0px`)。

5. **消しゴムボタン選択**

   * **消しゴム** をトグル。

     * ON 時: **キャンバス1.PaintColor ← 白**。
     * OFF 時: **キャンバス1.PaintColor ← 選択色ボタン.BackgroundColor** に戻す。

# イベント駆動フロー(重要)

* **Screen1.Initialize**

  * `色ボタン選択(選択色ボタン)` を最初に呼び(既定は **黒**)、
  * `太さボタン選択(ペン太さ)` を次に呼ぶ(既定は **細**)。

* **任意の Button.Click(Any Button)**
  クリックされた **component** に応じて分岐:

  1. `色ボタンリスト` に含まれる →
     **色ボタンリセット** → **消しゴム ← false** → **色ボタン選択(component)**
  2. `太さボタンリスト` に含まれる →
     **太さボタンリセット** → **太さボタン選択(component)**
  3. **画面消去** → **キャンバス1.Clear()**
  4. **消しゴム** → **消しゴムボタン選択()**

* **キャンバス1.Dragged(prevX, prevY, currentX, currentY, ...)**

  * **キャンバス1.DrawLine(prevX, prevY, currentX, currentY)**
    (線の色=`PaintColor`、太さ=`LineWidth` はボタン操作で設定済み)

# ブロック統計(Screen1.bky)

* 総ブロック数: **126**
* 代表的なブロック種別(上位)

  * `lexical_variable_get` 34 / `component_component_block` 14 / `component_set_get` 14
  * `global_declaration` 7 / `procedures_callnoreturn` 7 / `procedures_defnoreturn` 5
  * 制御系 `controls_if` 4 / `controls_forEach` 2
  * 色定数: `color_black` 3, `color_white` 2, `color_red` 2 など
* コンポーネントメソッド呼び出し

  * **Canvas.DrawLine**(ドラッグ時の描画)
  * **Canvas.Clear**(「画面消去」)

# 擬似コード(挙動イメージ)

```text
On Initialize:
  色ボタン選択(選択色ボタン=黒)
  太さボタン選択(ペン太さ=細)

Any Button.Click(component):
  if component ∈ 色ボタンリスト:
    色ボタンリセット()
    消しゴム := false
    色ボタン選択(component)          // ✔︎を付けて PaintColor を更新
  else if component ∈ 太さボタンリスト:
    太さボタンリセット()
    太さボタン選択(component)        // TextColor=赤, LineWidth を更新
  else if component == 画面消去:
    Canvas.Clear()
  else if component == 消しゴム:
    消しゴムボタン選択()             // 白/元色 へ PaintColor 切替

Canvas.Dragged(prevX, prevY, x, y):
  Canvas.DrawLine(prevX, prevY, x, y)
```

# 仕様上のポイント/注意

* **色選択と消しゴムは排他**:色ボタンを押すと必ず **消しゴム=false** に戻り、色の BackgroundColor が `PaintColor` に復帰します。
* **太さは 2 段階**(3px / 5px)。必要に応じて `太さボタンリスト` と `太さリスト` に要素を追加すれば拡張容易。
* **選択状態の可視化**:

  * 色…✔︎(Text)と TextColor(白/黒)で表現
  * 太さ…Button の TextColor(赤)で表現
* **永続化や共有は未実装**:描画の保存(画像出力/共有)、Undo/Redo、ズームなどはなし。

# 改善提案(実装のしやすさ順)

1. **太さ段階の拡張**:`太さボタンリスト` と `太さリスト` に「太」「極太」などを追加。
2. **Undo/Redo**:ドラッグで引いた線分の履歴(座標+色+太さ)をスタックに積む設計。
3. **保存/共有**:`Canvas.Save`(拡張/外部コンポーネント)や `ActivityStarter` 連携で画像保存・共有。
4. **筆圧/スムージング**:ドラッグ点列の間引きや補間、丸めを入れて滑らかなストロークに。
5. **UI アクセシビリティ**:選択強調をテキスト色だけに依存せず、アウトラインやアイコン化で視認性UP。
6. **消しゴムの太さ別管理**:消しゴム使用中の LineWidth を色ペンとは別に持つと使い勝手向上。

# 解析アーカイブ(生データ)

必要であれば、内部ファイルを直接ご確認いただけます:

* [Screen1.scm(JSON 抜粋テキスト)](sandbox:/mnt/data/Screen1_scm.txt)
* Blockly XML(抜粋)

  * [Screen1.Initialize イベント](sandbox:/mnt/data/event_init.xml)
  * [Any Button.Click イベント](sandbox:/mnt/data/event_any_button_click.xml)
  * [手続き: 色ボタン選択](sandbox:/mnt/data/proc_color_select.xml)
  * [手続き: 太さボタン選択](sandbox:/mnt/data/proc_thick_select.xml)
  * [手続き: 消しゴムボタン選択](sandbox:/mnt/data/proc_erasure.xml)
* コンポーネント一覧(CSV): [components.csv](sandbox:/mnt/data/components.csv)

---

さらに深掘り(たとえば「Undo をこう設計して」「太さを 4 段階にしてブロックを出して」等)も、そのままこちらで具体ブロックをご提案できます。どこから手を付けるか、希望があれば教えてください!

要件定義書作成

プロンプト

このアプリの要件定義書をMarkdownで書いて

返答

了解しました。先ほど解析した **DoodlePlus.aia** の内容を基に、ソフトウェアエンジニアが利用できる形式の **要件定義書(Markdown)** をまとめました。

---

# 要件定義書

## プロジェクト概要

* **アプリ名**: DoodlePlus
* **概要**: スマートフォンやタブレット上で指先を用いて自由にお絵かきできるアプリ。色や線の太さを変更でき、消しゴムや画面消去機能を備える。
* **対象プラットフォーム**: Android(MIT App Inventor 2 ベース)
* **バージョン**: 1.0

---

## 目的

* 子供から大人まで誰でも直感的に絵を描ける「シンプルなお絵かきアプリ」を提供する。
* 学習や創作の補助ツールとして利用可能。

---

## 機能要件

### 描画機能

* 指のドラッグ操作により線を描画できる。
* 線の色と太さをユーザーが選択可能。

### 色選択

* 黒、青、緑、赤の 4 色から選択可能。
* 選択中の色は ✔︎ マークで可視化される。
* 色ボタンを選択した際、消しゴムモードは解除される。

### 太さ選択

* 細(3px)、中(5px)の 2 段階。
* 選択中の太さは赤文字で表示される。

### 消しゴム機能

* ON/OFF 切り替え可能。
* ON 時はキャンバスの背景色(白)で描画し、既存の線を消せる。
* OFF 時は直前に選択した色へ戻る。

### 画面消去

* ワンタップでキャンバス全体をクリアする。

---

## 非機能要件

* **UI**: シンプルで直感的。文字よりも色やアイコンを重視。
* **操作性**: 子供でも使える直感的な操作。
* **パフォーマンス**: 描画時に遅延が発生しないこと。
* **可搬性**: Android スマホ/タブレット両対応。

---

## UI設計

### 画面構成

1. **キャンバス領域**

   * 画面全体に広がる描画スペース。

2. **色選択バー(水平配置1)**

   * 黒、青、緑、赤のボタン。

3. **太さ選択バー(水平配置2)**

   * 細、中のボタン。

4. **操作バー(水平配置3)**

   * 「画面消去」ボタン。
   * 「消しゴム」ボタン。

---

## データ要件

* 永続化なし(保存機能なし)。
* アプリ起動ごとにキャンバスは初期化される。

---

## 制約条件

* MIT App Inventor 2 による開発。
* デバイス依存: Android 4.4 以上を想定。

---

## 今後の拡張要件(任意)

* 線の太さ段階の追加(太、極太)。
* 描画内容の保存(端末ストレージ/共有)。
* Undo/Redo 機能。
* カラーパレットによる色追加。

---

---

この文書は「現状の DoodlePlus.aia の実装仕様」=「要件定義書」としてまとめています。

👉 ご希望があれば、このまま **設計書(UIフローや処理シーケンス図付き)** に発展させることも可能です。やりますか?

JavaScriptプログラミング

プロンプト

この要件定義書に基づいてJavaScriptのコードを書いて。初心者にも何をしているかわかるように、また、App Inventorアプリとの対応関係がわかるように、詳しいコメントをつけて。

返答

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>DoodlePlus (JavaScript 版)</title>
  <style>
    /* =============================
       シンプルな見た目のためのCSS
       ============================= */
    :root {
      --primary: #3f51b5; /* App Inventor プロジェクト設定のPrimary色に合わせた雰囲気 */
      --accent: #ff4081;
      --bg: #ffffff;
      --text: #222;
      --muted: #888;
    }
    * { box-sizing: border-box; }
    html, body { height: 100%; margin: 0; background: var(--bg); color: var(--text); font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans JP", "Hiragino Kaku Gothic ProN", Meiryo, sans-serif; }
    main { display: grid; grid-template-rows: auto 1fr; height: 100%; }
    header { padding: .5rem .75rem; border-bottom: 1px solid #eee; display: flex; align-items: center; gap: .75rem; }
    header h1 { font-size: 1rem; font-weight: 600; margin: 0; color: var(--primary); }

    /* ツールバー(App Inventorの「水平配置」に相当) */
    #toolbar { display: flex; gap: .75rem; padding: .5rem; border-bottom: 1px solid #eee; flex-wrap: wrap; }
    .group { display: flex; gap: .5rem; align-items: center; }
    .group-label { font-size: .85rem; color: var(--muted); }

    /* ボタン(App InventorのButtonに相当) */
    button { appearance: none; border: 1px solid #ddd; background: #fff; color: #222; padding: .4rem .6rem; border-radius: .5rem; cursor: pointer; font-size: .95rem; }
    button:focus { outline: 2px solid #d0d8ff; outline-offset: 2px; }
    button.selected { border-color: var(--primary); box-shadow: 0 0 0 2px rgba(63,81,181,0.12) inset; }

    /* 色ボタンは背景色で色を示す。選択状態はチェックマークで表現(App InventorのTextに"✔︎"を出すのに対応) */
    .color-btn { width: 44px; height: 32px; border-radius: .5rem; position: relative; }
    .color-btn .check { position: absolute; right: 6px; top: 4px; font-size: 16px; font-weight: 700; color: #fff; text-shadow: 0 1px 2px rgba(0,0,0,.35); display: none; }
    .color-btn.selected .check { display: block; }

    /* 太さボタンは選択中を赤文字(App InventorのTextColor=赤)で表す */
    .thick-btn.selected { color: #d32f2f; font-weight: 700; }

    /* アクションボタン */
    #eraser.selected { background: #fafafa; border-color: #bbb; }
    #clear { border-color: #ffe0e0; }

    /* キャンバス(App InventorのCanvasに相当) */
    #stage-wrap { position: relative; }
    #stage { width: 100%; height: 100%; display: block; background: #fff; touch-action: none; /* ジェスチャーでのスクロール/ズームを抑制 */ }

    /* 小さなヘルプ */
    footer { padding: .35rem .75rem; font-size: .8rem; color: var(--muted); border-top: 1px solid #eee; }
  </style>
</head>
<body>
  <main>
    <!-- App Inventor: Screen1.Title 相当 -->
    <header>
      <h1>お絵かきアプリ+(DoodlePlus / JS版)</h1>
      <span style="font-size:.85rem;color:#888">App Inventorのブロック構成をJavaScript実装に写経</span>
    </header>

    <!-- App Inventor: 水平配置1/2/3 をまとめたツールバー -->
    <div id="toolbar" role="toolbar" aria-label="Drawing toolbar">
      <!-- 色ボタン群(水平配置1) -->
      <div class="group" id="colors" aria-label="colors">
        <span class="group-label">色:</span>
        <button class="color-btn" id="btn-black"  data-color="#000000" title="黒" aria-pressed="true" style="background:#000000"><span class="check">✔︎</span></button>
        <button class="color-btn" id="btn-blue"   data-color="#0000ff" title="青" style="background:#0000ff"><span class="check">✔︎</span></button>
        <button class="color-btn" id="btn-green"  data-color="#00ff00" title="グリーン" style="background:#00ff00"><span class="check">✔︎</span></button>
        <button class="color-btn" id="btn-red"    data-color="#ff0000" title="赤" style="background:#ff0000"><span class="check">✔︎</span></button>
      </div>

      <!-- 太さボタン群(水平配置2) -->
      <div class="group" id="thickness" aria-label="thickness">
        <span class="group-label">太さ:</span>
        <button class="thick-btn" id="btn-thin"   data-width="3">細</button>
        <button class="thick-btn" id="btn-medium" data-width="5">中</button>
      </div>

      <!-- アクション群(水平配置3) -->
      <div class="group" id="actions" aria-label="actions">
        <button id="clear" title="画面消去 (Canvas.Clear)">画面消去</button>
        <button id="eraser" title="消しゴム (白で描く)">消しゴム</button>
      </div>
    </div>

    <!-- キャンバス領域(Canvas) -->
    <div id="stage-wrap">
      <canvas id="stage"></canvas>
    </div>

    <footer>
      ヒント: ドラッグして線を描画。色を押すと消しゴムは解除されます(App Inventorの仕様を踏襲)。
    </footer>
  </main>

  <script>
    // =============================================================
    //  App Inventorの状態と手続きをJavaScriptで表現
    // =============================================================
    // ▼App Inventorでのグローバル変数に相当
    //   - 選択色ボタン(Button参照)
    //   - 色ボタンリスト
    //   - ペン太さ(Button参照)
    //   - 太さボタンリスト
    //   - 太さリスト [3.0, 5.0]
    //   - 消しゴム(boolean)

    const colorButtons = [
      document.getElementById('btn-black'),
      document.getElementById('btn-blue'),
      document.getElementById('btn-green'),
      document.getElementById('btn-red')
    ];
    let selectedColorButton = colorButtons[0]; // 既定は「黒」

    const thicknessButtons = [
      document.getElementById('btn-thin'),
      document.getElementById('btn-medium')
    ];
    const thicknessList = [3, 5]; // App Inventor: 太さリスト [3.0, 5.0]
    let selectedThicknessButton = thicknessButtons[0]; // 既定は「細」

    let eraser = false; // 消しゴムモードかどうか

    // ▼Canvas関連
    const canvas = document.getElementById('stage');
    const ctx = canvas.getContext('2d');

    // 高DPIでもクッキリ描けるように、CSSサイズと内部ピクセルを分けて設定
    function resizeCanvasToDisplaySize() {
      const dpr = window.devicePixelRatio || 1;
      const rect = canvas.getBoundingClientRect();
      const width = Math.floor(rect.width * dpr);
      const height = Math.floor(rect.height * dpr);
      if (canvas.width !== width || canvas.height !== height) {
        // 既存の描画は消えるので、必要ならイメージを保持してからリサイズしてください。
        const img = ctx.getImageData(0, 0, canvas.width, canvas.height);
        canvas.width = width;
        canvas.height = height;
        ctx.putImageData(img, 0, 0); // 単純復元(拡大縮小はしない)
        ctx.scale(dpr, dpr); // 以降の描画はCSSピクセル基準
      }
      // 背景が透明になっている場合、白で塗りつぶしてApp Inventorの見た目に合せる
      // (初回のみ塗る・クリア時は別処理)
    }

    // レイアウト: ツールバーを除いた残りの領域いっぱいにキャンバスを広げる
    function layout() {
      const headerH = document.querySelector('header').offsetHeight;
      const toolbarH = document.getElementById('toolbar').offsetHeight;
      const footerH = document.querySelector('footer').offsetHeight;
      const wrap = document.getElementById('stage-wrap');
      wrap.style.height = `calc(100vh - ${headerH + toolbarH + footerH}px)`;
      resizeCanvasToDisplaySize();
    }

    window.addEventListener('resize', layout);

    // =============================================================
    //  App Inventor: 手続き(procedures)の移植
    // =============================================================
    // 手続き: 色ボタンリセット(全ボタンから✔︎を外す)
    function resetColorButtons() {
      colorButtons.forEach(btn => btn.classList.remove('selected'));
      colorButtons.forEach(btn => btn.setAttribute('aria-pressed', 'false'));
    }

    // 手続き: 色ボタン選択(ボタン)
    function selectColorButton(btn) {
      // App Inventor: 選択色ボタン.TextColor=白 相当 → ここではUI強調として✔︎表示
      btn.classList.add('selected');
      btn.setAttribute('aria-pressed', 'true');
      selectedColorButton = btn;

      // キャンバスの線色(PaintColor)を更新
      updateStrokeStyle();
    }

    // 手続き: 太さボタンリセット(全ボタンのTextColor=黒 相当)
    function resetThicknessButtons() {
      thicknessButtons.forEach(btn => btn.classList.remove('selected'));
    }

    // 手続き: 太さボタン選択(ボタン)
    function selectThicknessButton(btn) {
      btn.classList.add('selected'); // App Inventor: TextColor=赤 相当
      selectedThicknessButton = btn;
      // LineWidthを更新
      updateLineWidth();
    }

    // 手続き: 消しゴムボタン選択(トグル)
    function toggleEraser() {
      eraser = !eraser;
      document.getElementById('eraser').classList.toggle('selected', eraser);
      updateStrokeStyle(); // PaintColorを白 or 選択色へ
    }

    // 付帯: 線色を状態に合わせて更新
    function updateStrokeStyle() {
      if (eraser) {
        ctx.strokeStyle = '#ffffff'; // App Inventor: 白で上書き
      } else {
        ctx.strokeStyle = selectedColorButton.dataset.color;
      }
    }

    // 付帯: 線の太さを状態に合わせて更新
    function updateLineWidth() {
      const w = Number(selectedThicknessButton.dataset.width) || thicknessList[0];
      ctx.lineWidth = w;
      ctx.lineCap = 'round';
      ctx.lineJoin = 'round';
    }

    // 付帯: 画面消去(Canvas.Clear)
    function clearCanvas() {
      // 背景を白で塗りつぶし、App InventorのCanvas.Clearに近い見た目に
      ctx.save();
      ctx.setTransform(1,0,0,1,0,0); // 変換を一時的にリセット
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      // 白背景
      ctx.fillStyle = '#ffffff';
      const rect = canvas.getBoundingClientRect();
      ctx.fillRect(0, 0, rect.width, rect.height);
      ctx.restore();
    }

    // =============================================================
    //  App Inventor: Screen1.Initialize 相当(初期化)
    // =============================================================
    function initialize() {
      layout();
      // 既定の選択状態をApp Inventorと同じにする
      resetColorButtons();
      selectColorButton(selectedColorButton); // 黒

      resetThicknessButtons();
      selectThicknessButton(selectedThicknessButton); // 細(3px)

      // 背景を白で初期化
      clearCanvas();
    }

    // =============================================================
    //  App Inventor: Any Button.Click 相当のイベント分岐
    // =============================================================
    function onButtonClick(e) {
      const el = e.target.closest('button');
      if (!el) return;

      if (colorButtons.includes(el)) {
        // 色ボタン: 色ボタンリセット → 消しゴム=false → 色ボタン選択
        resetColorButtons();
        eraser = false;
        document.getElementById('eraser').classList.remove('selected');
        selectColorButton(el);
        return;
      }

      if (thicknessButtons.includes(el)) {
        // 太さボタン: 太さボタンリセット → 太さボタン選択
        resetThicknessButtons();
        selectThicknessButton(el);
        return;
      }

      if (el.id === 'clear') {
        clearCanvas();
        return;
      }

      if (el.id === 'eraser') {
        toggleEraser();
        return;
      }
    }

    document.getElementById('toolbar').addEventListener('click', onButtonClick);

    // =============================================================
    //  App Inventor: Canvas.Dragged(prevX, prevY, currentX, currentY)
    //  → JSではPointer Eventsで実装
    // =============================================================
    let drawing = false;
    let last = { x: 0, y: 0 };

    function toCanvasCoords(evt) {
      const rect = canvas.getBoundingClientRect();
      const x = (evt.clientX - rect.left);
      const y = (evt.clientY - rect.top);
      return { x, y };
    }

    function pointerDown(evt) {
      drawing = true;
      last = toCanvasCoords(evt);
    }

    function pointerMove(evt) {
      if (!drawing) return;
      const p = toCanvasCoords(evt);
      // Canvas.DrawLine(prevX, prevY, x, y) 相当
      ctx.beginPath();
      ctx.moveTo(last.x, last.y);
      ctx.lineTo(p.x, p.y);
      ctx.stroke();
      last = p;
    }

    function pointerUp() {
      drawing = false;
    }

    canvas.addEventListener('pointerdown', pointerDown);
    canvas.addEventListener('pointermove', pointerMove);
    window.addEventListener('pointerup', pointerUp);

    // スクロールなどで外に出た場合の保険
    canvas.addEventListener('pointerleave', pointerUp);

    // =============================================================
    // 起動
    // =============================================================
    initialize();
  </script>
</body>
</html>
できました!JS版の実装をキャンバスに置きました。

App Inventorの Screen1 / Canvas / 水平配置 / Button / 手続き を、それぞれ HTML要素 / <canvas> / ツールバー群 / <button> / JS関数 に対応づけ、ブロック名どおりの処理と詳しい日本語コメントを入れています。

そのままブラウザで動きます。色ボタンで✔︎が出て、太さは選択で赤表示、消しゴムは白で上書き、画面消去は白塗りクリアです。

他に欲しい拡張(保存・Undo/Redo・太さ追加・色数拡張・モバイル最適化など)があれば、この実装をベースにすぐに組み込みます。

動作確認

DoodlePlus.htmlに保存しGoogle Chromeで実行してみました。言葉が少し変なところもありますが、動作自体は以下のように問題無いです。

公開済みAI関連アプリチュートリアル:シルバー会員特典

ゴールド会員特典はAIが中心

初心者にはこちらも

ChatGPT,claude,gemini,アプリ開発,ノート,生成AI

考えたことメモ > App InventorからPythonへどうやって移行するのか?

Google Gemini 2.5 Pro Deep Researchによれば、「一度App Inventorで作ったアプリをPythonで再現してみるというアプローチは、目的が明確なため非常に効果的な学習方法です」とのことでした。これは、AI時代における実践的なプログラミング学習の一例といえます。

Pythonは文法が比較的シンプルで、初心者にも扱いやすい言語です。AI開発、データ分析、Webサイト制作、作業の自動化など、幅広い分野に応用できる点が特徴です。

今回は、出発点として「オンデマンドアカデミー」で紹介されている カラフルお絵かきアプリを題材に選びました。このアプリは、基本的なUIの構成やイベント処理の理解に適しており、Pythonでの再構築を目標に据えるにはちょうどよい教材です。

どこまでAIが支援できるかを検討するため、Claude Sonnet 4, Gemini 2.5 Pro, ChatGPT-4oを比較対象に選びました。これらのAIを活用しながら、App InventorアプリをPythonでどう再現していけるのか、段階的に分析していきます。

aiaファイルの読み込みと解析

要件定義書作成とPythonプログラミング

動作確認

サマリー

ChatGPT-4oはApp Inventorのaiaファイルを直接読み込み、アプリの要件定義書を書いてそのまま動作するPythonコードを書けました。

aiaファイルの読み込みと解析

aiaファイルをアップロードして正しく認識できるかをClaude Sonnet 4, Gemini 2.5 Pro, ChatGPT-4oで試してみました。ChatGPT-4oだけが、正しくaiaファイルを解析できました。

Claude Sonnet 4

Gemini 2.5 Pro

ChatGPT-4o

Claude Sonnet 4

aiaファイルのアップロード不可。


Gemini 2.5 Pro

プロンプト

あなたはApp Inventor 2の内部構造に精通したプロのソフトウェアエンジニアです。このaiaファイルを完全に解析してください。

返答

はい、承知いたしました。提供された `DoodlePlus.aia` ファイルを専門的な観点から解析します。

このファイルは、MIT App Inventor 2で作成されたプロジェクトのアーカイブファイルです。`.aia` ファイルは実質的にZIP形式のアーカイブであり、プロジェクトを構成するすべてのファイル(画面デザイン、プログラミングロジック、アセットなど)を含んでいます。

提供された情報から、このプロジェクトの構造と内容を以下のように解析します。

### プロジェクトの概要

* [cite_start]**プロジェクト名**: `DoodlePlus` [cite: 1]
* [cite_start]**作成者アカウント**: `ai_XXXXXXX` [cite: 1]
* [cite_start]**プラットフォーム**: MIT App Inventor 2 [cite: 1]
* **内容の推測**: プロジェクト名に「Doodle」(いたずら書き)とあることから、これは指で画面に絵を描くことができる**お絵かきアプリケーション**であると強く推測されます。「Plus」という接尾辞は、基本的なお絵かき機能に加えて、何らかの追加機能(色の変更、線の太さの調整、保存機能など)が実装されている可能性を示唆しています。

---

### ファイル構造の解析

AIAファイルは、主に以下の3種類のコアファイルで構成されています。このプロジェクトには `Screen1` という名前の画面が少なくとも1つ存在します。

#### 1. `youngandroidproject/project.properties`
[cite_start]このファイルは、プロジェクト全体の設定を定義するプロパティファイルです。 [cite: 1] テキスト形式で、以下のようなキーと値のペアが含まれています。

* `name`: プロジェクト名(この場合は `DoodlePlus`)。
* `versioncode` & `versionname`: アプリのバージョン情報。
* `theme`: アプリケーションの全体的なデザインテーマ(例: "Classic" または "DeviceDefault")。
* `user`: プロジェクトの作成者アカウント(この場合は `ai_tam0130`)。

#### 2. `src/appinventor/ai_tam0130/DoodlePlus/Screen1.scm`
[cite_start]これは、`Screen1` の**UIデザイン**を定義するファイルです。 [cite: 1] SCMは "Scheme" の略ですが、実際にはJSONに似た形式で画面上のコンポーネントの階層と各プロパティを記述しています。

* **役割**: どのコンポーネント(ボタン、ラベル、キャンバスなど)が画面に配置されているか、またそのプロパティ(サイズ、色、テキストなど)がどう設定されているかを定義します。
* **推測されるコンポーネント**: お絵かきアプリであるため、このファイルにはおそらく以下のようなコンポーネントの定義が含まれています。
    * `Canvas`: ユーザーが絵を描くための主要な描画領域。
    * `Button`: 「色変更」「消去」「保存」などの機能を実行するためのボタン。
    * `HorizontalArrangement` / `VerticalArrangement`: ボタンなどのUI要素を整理・配置するためのレイアウトコンポーネント。
    * `Slider`: 線の太さなどを直感的に変更するために使われる可能性があります。

#### 3. `src/appinventor/ai_tam0130/DoodlePlus/Screen1.bky`
[cite_start]これは、`Screen1` の**プログラミングロジック**を定義する最も重要なファイルです。 [cite: 1] GoogleのBlocklyライブラリをベースにしており、ブロックで組まれたプログラムがXML形式で保存されています。

* **役割**: アプリケーションの動作(「いつ」「何が」「どうなるか」)を定義します。例えば、「ボタンがクリックされたら、キャンバスの色を赤に変える」といったロジックが記述されます。
* **推測されるロジック**: このXMLファイルには、以下のようなイベントハンドラやプロシージャが含まれていると考えられます。
    * `when Canvas.Dragged do ...`: キャンバス上で指がドラッグされたときのイベント。この中で `Canvas.DrawLine` ブロックを使い、指の動きに合わせて線を描画する処理が記述されているはずです。
    * `when ColorButton.Click do ...`: 特定の色のボタンがクリックされたときに、`Canvas.PaintColor` プロパティを対応する色に変更する処理。
    * `when ClearButton.Click do ...`: 消去ボタンがクリックされたときに、`Canvas.Clear` メソッドを呼び出してキャンバスを白紙に戻す処理。

---

### 結論

提供された `DoodlePlus.aia` は、`ai_XXXXXX` というユーザーによって作成された、`Screen1` という単一の画面を持つ**インタラクティブなお絵かきアプリケーション**のプロジェクトファイルです。

このプロジェクトは、App Inventor 2の標準的なアーキテクチャに従っており、UIデザイン (`.scm`) とイベント駆動型のロジック (`.bky`) が明確に分離されています。提供されたデータはファイル内容の断片であるため、具体的なコンポーネントのプロパティ値やブロックの組み合わせを完全に復元することはできません。しかし、そのファイル構造と命名規則から、アプリケーションの全体像と主要な機能を専門的に特定することが可能です。

プロンプト

中身を全部詳細に解析して

返答

はい、承知いたしました。
前回の解析に基づき、各ファイルに**通常どのようなコードが記述されているか**を、具体的なサンプルを交えてさらに詳細に解析します。

提供されたファイルの中身はデータが破損しているため直接読み解くことはできませんが、App Inventorのエンジニアとして、その構造から**中身を具体的に復元・推測**することは可能です。

### 🎨 DoodlePlus アプリの全体像

このアプリは、指で絵が描けるキャンバスと、色や線の太さを変更したり、絵を消したりするためのボタン群で構成されていると推測されます。

-----

### \#\# 1. UIデザイン (`Screen1.scm` の詳細解析)

このファイルは、画面の見た目を定義します。JSON形式で、どのコンポーネントが、どのようなプロパティ(設定値)で配置されているかが記述されています。

`DoodlePlus` の場合、`Screen1.scm` の中身は以下のようになっていると考えられます。

```json
{
  "$Name": "Screen1",
  "$Type": "Form",
  "$Version": "...",
  "Title": "DoodlePlus",
  "components": [
    {
      "$Name": "DrawingCanvas",
      "$Type": "Canvas",
      "Width": -2, // -2は「親に合わせる(Fill Parent)」
      "Height": -2,
      "PaintColor": "#FF000000" // 初期色は黒
    },
    {
      "$Name": "ControlPanel",
      "$Type": "HorizontalArrangement", // ボタンを水平に並べるレイアウト
      "Width": -2,
      "components": [
        {
          "$Name": "RedButton",
          "$Type": "Button",
          "Text": "Red",
          "BackgroundColor": "#FFFF0000"
        },
        {
          "$Name": "BlueButton",
          "$Type": "Button",
          "Text": "Blue",
          "BackgroundColor": "#FF0000FF"
        },
        {
          "$Name": "ClearButton",
          "$Type": "Button",
          "Text": "Clear"
        }
      ]
    },
    {
      "$Name": "LineWidthSlider", // 「Plus」機能の可能性
      "$Type": "Slider",
      "Width": -2,
      "MaxValue": "30.0",
      "MinValue": "2.0",
      "ThumbPosition": "5.0" // 初期値の線の太さ
    }
  ]
}
```

**【ポイント】**

  * **`DrawingCanvas`**: 絵を描くための主要な領域です。幅と高さを「親に合わせる」に設定し、画面いっぱいに広げている可能性が高いです。
  * **`ControlPanel`**: 色変更ボタンや消去ボタンを横一列に並べるための入れ物です。
  * **`RedButton`, `BlueButton` など**: クリックすると線の色が変わるボタンです。
  * **`LineWidthSlider`**: 「Plus」機能の核心部分で、スライダーを動かすことで線の太さを直感的に変更できるようにしていると推測されます。

-----

### \#\# 2. プログラムロジック (`Screen1.bky` の詳細解析)

このファイルは、アプリの実際の動作を定義するブロックプログラム(XML形式)です。上記のUIコンポーネントが、ユーザーの操作によってどのように動くかが記述されています。

#### **中核機能:線を描くロジック**

これが最も基本的な機能です。キャンバス上で指をドラッグした時に、その軌跡に線を引きます。

  * **イベント**: `when DrawingCanvas.Dragged` (DrawingCanvasがドラッグされた時)
  * **アクション**: `call DrawingCanvas.DrawLine` (線を引く)
      * `x1`, `y1`: 前の座標 (prevX, prevY)
      * `x2`, `y2`: 現在の座標 (currentX, currentY)

#### **色を変更するロジック**

各色のボタンが押された時に、キャンバスの `PaintColor` (描画色) プロパティを変更します。

  * **イベント**: `when RedButton.Click` (RedButtonがクリックされた時)

  * **アクション**: `set DrawingCanvas.PaintColor to` (描画色をセットする)

      * **値**: 赤色のカラーブロック

  * **イベント**: `when BlueButton.Click` (BlueButtonがクリックされた時)

  * **アクション**: `set DrawingCanvas.PaintColor to`

      * **値**: 青色のカラーブロック

#### **全消去するロジック**

消去ボタンが押された時に、キャンバスの内容をすべてクリアします。

  * **イベント**: `when ClearButton.Click` (ClearButtonがクリックされた時)
  * **アクション**: `call DrawingCanvas.Clear` (キャンバスをクリアする)

#### **「Plus」機能:線の太さを変更するロジック**

スライダーの位置が変わった時に、キャンバスの `LineWidth` (線の太さ) プロパティを変更します。

  * **イベント**: `when LineWidthSlider.PositionChanged` (LineWidthSliderの位置が変わった時)
  * **アクション**: `set DrawingCanvas.LineWidth to` (線の太さをセットする)
      * **値**: `get thumbPosition` (スライダーの現在の値)

### **結論**

この `DoodlePlus.aia` は、単に線が描けるだけでなく、**「複数の色への変更機能」「全消去機能」「スライダーによる線の太さの動的な変更機能」** を備えた、多機能お絵かきアプリであると結論付けられます。UI定義ファイル (`.scm`) とロジック定義ファイル (`.bky`) が連携し、これらの機能を実現しています。

ハルシネーションがひどい。できないならできないとはっきり言って欲しい。

  • 提供されたファイルの中身はデータが破損しているため直接読み解くことはできませんが、App Inventorのエンジニアとして、その構造から中身を具体的に復元・推測することは可能です。
  • Screen1.scm の中身は以下のようになっていると考えられます。

ChatGPT-4o

プロンプト

あなたはApp Inventor 2の内部構造に精通したプロのソフトウェアエンジニアです。このaiaファイルを完全に解析してください。

返答

`.aia`ファイルはApp InventorプロジェクトのZIP形式のアーカイブです。`DoodlePlus.aia`を展開したところ、次の主要ファイルが含まれていました:

* `src/appinventor/ai_tam0130/DoodlePlus/Screen1.bky`: ブロックエディタの情報(Blockly形式のデータ)
* `src/appinventor/ai_tam0130/DoodlePlus/Screen1.scm`: 画面のUI構造とプロパティ設定(YAILコード)
* `youngandroidproject/project.properties`: プロジェクト全体の設定情報

次に、各ファイルの内容を解析して、どのようなアプリなのか、どんな機能があるのかを詳細に説明します。まずは `.scm` ファイルから見て、ユーザーインターフェースの構成と設定を確認します。


このアプリ「DoodlePlus」のUI構成(`Screen1.scm`)から、以下のような構成であることがわかります:

### アプリ情報

* **アプリ名**:DoodlePlus(表示タイトルは「お絵かきアプリ+」)
* **スクリーン名**:Screen1
* **バージョン**:31

---

### 主なUIコンポーネント

#### 1. `Canvas1`(キャンバス)

* 描画用のキャンバス。ユーザーが絵を描くエリア。

#### 2. `HorizontalArrangement1`(水平配置1)色選択ボタン群

* 黒(背景色:`#000000`)
* 青(`#0000FF`)
* 緑(`#00FF00`)
* 赤(`#FF0000`)

#### 3. `HorizontalArrangement2`(水平配置2)線の太さ選択

* 「細」(テキスト:細)
* 「中」(テキスト:中)

#### 4. `HorizontalArrangement3`(水平配置3)描画操作

* 「画面消去」:キャンバスをクリアする
* 「消しゴム」:消去モードへの切り替えと推測

---

次に、ブロックエディタ側の `.bky` ファイル(`Screen1.bky`)を解析して、このUIにどのようなロジックや動作が関連付けられているのかを調べます。


`.bky`ファイルの冒頭を解析した結果、このアプリは明確なロジック構造を持ち、描画操作を柔軟に制御できるようになっています。

---

### **ブロックエディタの主要ロジック(抜粋)**

#### 🔷 グローバル変数の定義

* `選択マーク`: `"✔︎"` — ボタン選択時に表示するチェックマーク。
* `選択色ボタン`: 初期状態は「黒」ボタン。
* `色ボタンリスト`: 「黒」「青」「グリーン」「赤」の4色ボタン。
* `ペン太さ`: 初期状態は「細」ボタン。
* `太さボタンリスト`: 「細」「中」の太さ選択ボタン。

---

### 🎨 推定される機能と動作

1. **色の選択**

   * 色ボタン(黒・青・緑・赤)を押すと、選択された色で線が描かれる。
   * 押されたボタンに「✔︎」が表示され、他のボタンのマークは消える。

2. **線の太さ選択**

   * 「細」「中」ボタンを押して描画する線の太さを切り替え。
   * 同様に選択されたボタンに「✔︎」を付加。

3. **消去機能**

   * 「画面消去」ボタン:Canvas全体をクリア。
   * 「消しゴム」ボタン:描画色を背景色と同じにして消すように描画。

---

### ✅ 結論:このアプリの概要

* **アプリ名**:「DoodlePlus(お絵かきアプリ+)」
* **主な機能**:

  * 指やタッチで絵を描く
  * 色・太さの選択
  * キャンバスの消去
  * 消しゴムモードの実装
* **技術的特徴**:

  * App InventorのCanvasコンポーネントを中心に、色ボタンと状態管理変数で操作性を実現
  * ユーザーフレンドリーなインターフェース設計(✔︎マーク付きの選択表現)

---

さらに詳しく(例:ブロック全体図や各イベント処理の詳細)をご希望の場合は、そのようにお知らせください。ご要望に応じて完全なフローチャートや動作の可視化も可能です。

要件定義書作成とPythonプログラミング

これ以降はChatGPT-4oだけで実施します。

プロンプト

このアプリの要件定義書をMarkdownで書いて

返答

以下は、`DoodlePlus` アプリの\*\*要件定義書(Markdown形式)\*\*です。App Inventorで開発されたお絵かきアプリの機能と仕様を整理しています。

---

# 📋 要件定義書:DoodlePlus(お絵かきアプリ+)

## 1. 概要

**DoodlePlus** は、タッチデバイス上で簡単に絵を描いたり消したりできるシンプルなお絵かきアプリです。ユーザーは色や線の太さを選択してキャンバス上に自由に描画でき、消しゴムや画面消去などの基本機能も備えています。

---

## 2. 目的

* 指やスタイラスで自由に絵を描ける環境を提供する。
* 子どもや初心者にも使いやすい直感的なUIを実現する。
* 色・太さ・消去といった基本的な描画操作をスムーズに行えるようにする。

---

## 3. ユースケース

| ユーザーアクション     | システムの反応                    |
| ------------- | -------------------------- |
| 色ボタンをタップ      | 線の描画色を変更、「✔︎」で選択中を表示       |
| 太さボタンをタップ     | 線の太さを変更、「✔︎」で選択中を表示        |
| キャンバス上をドラッグ   | 指の動きに応じて線を描画               |
| 「消しゴム」ボタンをタップ | ペンの色が背景色に変更され、描いた線を消すように動作 |
| 「画面消去」ボタンをタップ | キャンバス上のすべての描画をクリア          |

---

## 4. 画面仕様(UI構成)

### Screen1

| コンポーネント名   | 種類        | 説明                    |
| ---------- | --------- | --------------------- |
| Canvas1    | キャンバス     | 描画エリア。指やタッチで線を描ける。    |
| 黒、青、グリーン、赤 | ボタン(色選択)  | 線の色を変更。選択中は✔︎マークを表示。  |
| 細、中        | ボタン(太さ選択) | 線の太さを変更。選択中は✔︎マークを表示。 |
| 画面消去       | ボタン       | Canvasを全消去            |
| 消しゴム       | ボタン       | 線を背景色で描いて消す機能         |

---

## 5. 機能仕様

### 色の変更

* 対応色:黒、青、緑、赤
* 色ボタン押下時に線の描画色を即時変更
* 現在選択中のボタンには✔︎マークを追加

### 線の太さ変更

* 太さ:細、中(今後拡張可能)
* 現在選択中の太さボタンにも✔︎を追加

### 消しゴム

* 「消しゴム」押下時、ペン色をCanvasの背景色(白)に変更
* 色ボタンと✔︎の状態は保持

### 画面消去

* 「画面消去」ボタンを押すとCanvasの全描画を初期化

---

## 6. データ定義

### グローバル変数

| 変数名      | 用途               |
| -------- | ---------------- |
| 選択マーク    | ボタンに表示する選択記号(✔︎) |
| 選択色ボタン   | 現在選択中の色ボタンを保持    |
| 色ボタンリスト  | 色変更可能なボタン群(4色)   |
| ペン太さ     | 現在選択中の太さボタン      |
| 太さボタンリスト | 太さ変更可能なボタン群(細・中) |

---

## 7. 非機能要件

* **対応プラットフォーム**:Android(App Inventorによりビルド)
* **使用言語**:日本語(UIラベルおよびブロック)
* **パフォーマンス**:スムーズな描画処理(タッチに遅延なく追従)
* **操作性**:視認性の高いボタン、明確なフィードバック(✔︎)

---

## 8. 今後の拡張案

* 線の太さを3段階以上に
* カラーピッカー機能の追加
* 描画の保存・読み込み機能
* 子ども向けUIモードの強化(アイコン表示など)

---

必要に応じて、**画面遷移図**や**ブロック構成の図解**も作成できます。ご希望があればお知らせください。

プロンプト

この要件定義書に基づいてPythonのコードを書いて。初心者にも何をしているかわかるように、また、App Inventorアプリとの対応関係がわかるように、詳しいコメントをつけて。

返答

以下に、要件定義書に基づいた DoodlePlus アプリのPythonコード(tkinter を使用)を提示します。App Inventor のUI構成やブロックに相当する機能を意識して、初心者向けに丁寧なコメントを添えています。
✅ Python版「DoodlePlus」:App Inventor対応付き
📌 対応ライブラリ:tkinter(Python標準GUIライブラリ)
import tkinter as tk
from tkinter import ttk

# -------------------------
# アプリ設定と初期値の定義
# -------------------------

# キャンバス背景色(消しゴム用にも使用)
BACKGROUND_COLOR = "white"

# 初期ペン設定(黒・細)
current_color = "black"
current_width = 2  # 細い線
eraser_mode = False  # 消しゴムモード:初期はOFF

# 色選択用のボタン一覧(App Inventorの「色ボタンリスト」に相当)
color_buttons = {}

# 太さ選択用のボタン一覧(App Inventorの「太さボタンリスト」に相当)
width_buttons = {}

# 現在選択中のボタン(✔︎マーク用)を保持
selected_color_btn = None
selected_width_btn = None

# ✔︎マーク(選択中表示)
CHECK_MARK = "✔︎"

# -------------------------
# 線を描くときの関数定義
# -------------------------

def start_draw(event):
    """描画のスタート位置を記録"""
    canvas.old_x, canvas.old_y = event.x, event.y

def draw(event):
    """マウスドラッグに合わせて線を描画"""
    if not hasattr(canvas, "old_x") or not hasattr(canvas, "old_y"):
        return

    # 線の色と太さを使用して描画
    canvas.create_line(canvas.old_x, canvas.old_y, event.x, event.y,
                       width=current_width,
                       fill=current_color,
                       capstyle=tk.ROUND,
                       smooth=True)

    # 次回の起点を更新
    canvas.old_x, canvas.old_y = event.x, event.y

def reset_draw(event):
    """描画の終了:座標初期化"""
    canvas.old_x, canvas.old_y = None, None

# -------------------------
# 色・太さの変更機能
# -------------------------

def change_color(new_color, btn):
    """ペンの色を変更し、選択マークを更新"""
    global current_color, eraser_mode, selected_color_btn

    current_color = new_color
    eraser_mode = False

    # ✔︎の更新処理
    if selected_color_btn:
        selected_color_btn.config(text=selected_color_btn["text"].replace(CHECK_MARK, ""))
    btn.config(text=btn["text"] + CHECK_MARK)
    selected_color_btn = btn

def change_width(new_width, btn):
    """線の太さを変更し、選択マークを更新"""
    global current_width, selected_width_btn

    current_width = new_width

    # ✔︎の更新処理
    if selected_width_btn:
        selected_width_btn.config(text=selected_width_btn["text"].replace(CHECK_MARK, ""))
    btn.config(text=btn["text"] + CHECK_MARK)
    selected_width_btn = btn

# -------------------------
# 消しゴム・画面消去
# -------------------------

def use_eraser():
    """消しゴムモードに切り替え"""
    global current_color, eraser_mode
    current_color = BACKGROUND_COLOR
    eraser_mode = True

def clear_canvas():
    """Canvasを全消去"""
    canvas.delete("all")

# -------------------------
# UI作成(App Inventor対応UI)
# -------------------------

root = tk.Tk()
root.title("DoodlePlus(Python版)")

# Canvas:描画エリア(App InventorのCanvas1)
canvas = tk.Canvas(root, width=600, height=400, bg=BACKGROUND_COLOR)
canvas.grid(row=0, column=0, columnspan=6, pady=10)

# マウス操作のバインド
canvas.bind("<Button-1>", start_draw)
canvas.bind("<B1-Motion>", draw)
canvas.bind("<ButtonRelease-1>", reset_draw)

# -------------------------
# 色選択ボタン(App Inventor:黒・青・緑・赤)
# -------------------------

colors = [("黒", "black"), ("青", "blue"), ("グリーン", "green"), ("赤", "red")]
for i, (label, color) in enumerate(colors):
    btn = tk.Button(root, text=label, width=8, command=lambda c=color, b=label: change_color(c, color_buttons[b]))
    btn.grid(row=1, column=i)
    color_buttons[label] = btn

# 初期選択:黒に✔︎
color_buttons["黒"].config(text="黒" + CHECK_MARK)
selected_color_btn = color_buttons["黒"]

# -------------------------
# 太さ選択ボタン(App Inventor:細・中)
# -------------------------

widths = [("細", 2), ("中", 5)]
for i, (label, width) in enumerate(widths):
    btn = tk.Button(root, text=label, width=8, command=lambda w=width, b=label: change_width(w, width_buttons[b]))
    btn.grid(row=2, column=i)
    width_buttons[label] = btn

# 初期選択:細に✔︎
width_buttons["細"].config(text="細" + CHECK_MARK)
selected_width_btn = width_buttons["細"]

# -------------------------
# 消しゴム/画面消去ボタン
# -------------------------

eraser_btn = tk.Button(root, text="消しゴム", width=10, command=use_eraser)
eraser_btn.grid(row=2, column=2, padx=5)

clear_btn = tk.Button(root, text="画面消去", width=10, command=clear_canvas)
clear_btn.grid(row=2, column=3, padx=5)

# アプリ起動
root.mainloop()
📱 App InventorとPythonコードの対応表
App Inventorの要素	Pythonコードでの対応
Canvas1(描画)	tk.Canvas
色ボタン(黒〜赤)	change_color() 関数+ボタン配置
太さボタン(細・中)	change_width() 関数+ボタン配置
✔︎マークの表示	selected_color_btn / selected_width_btn の管理
消しゴム	use_eraser()(色を白に)
画面消去	clear_canvas()

💡補足
本コードはマウス操作向け(タッチでも動作するタブレットPC可)
App Inventorで言えば、すべてのボタンやキャンバスの「ブロック(処理)」に相当するものをPython関数で表現しています。

ご希望があれば:
画像の保存機能
カラーピッカー追加
タッチ対応強化(Kivyなど)
も実装できます!必要であればお知らせください。

動作確認

Windows 11でAnaconda のJupyter Notebookで実行してみました。一切変更なしで、以下のように問題なく動きました。

公開済みAI関連アプリチュートリアル:シルバー会員特典

ゴールド会員特典はAIが中心

初心者にはこちらも

AI,gemini,アプリ開発,チュートリアル,生成AI

最近特に賢くなってきている生成AIで簡単なアプリを作れるか、GoogleのGemini 2.0 Flash Thinking Experimentalで試してみました。結果は予想以上でGeminiは作り方のチュートリアル を出力してくれました。まだ100%これだけでOKというわけにはいきませんが、少しの調整で動くアプリを作れました。調整した点は以下です。

  • バーコードスキャナー外部スキャナーの使用のチェックを外す
  • BarcodeScanner1.AfterScanで「format」は渡されない

実際に作ったQRコード読み取りアプリのチュートリアルはこちらです。実験日: 2025年2月6日

App InventorでQRコードを読んで内容を表示するアプリ作れる?

残りのコンテンツを表示するにはログインする必要がありますどうぞ . 会員ではないですか ? 入会のご案内

gemini,アプリ開発,センサー,チュートリアル,ユーティリティ,生成AI

QRコードを読み取ってその内容を表示するアプリを作ります

このチュートリアルでは、QRコードを読み取ってその内容を表示するアプリを作ります。このチュートリアルのほとんどはGemini 2.0 Flash Thinking Experimentalが書きました。詳細はこちらです。

ページの最後にあるダウンロードセクションからソースコードをダウンロードできます。

[プロジェクト]メニューから[新規プロジェクトを始める]を選択し、"QRCodeReader“と名前を付けます。

コンポーネントの配置

画面左側の「パレット」からコンポーネントをドラッグ&ドロップして、アプリの画面(Viewer)に配置します。最低限必要なコンポーネントは以下の通りです。

  • バーコードスキャナー (BarcodeScanner): 「センサー」カテゴリの中にあります。これを画面上の見えない場所に配置します。画面には何も表示されません。プロパティ―の外部スキャナーの使用のチェックは外します。
  • ボタン (Button): 「ユーザーインターフェース」カテゴリの中にあります。これを画面上の好きな場所に配置します。このボタンをタップしてQRコードの読み取りを開始します。ボタンのテキストは「QRコードを読み取る」など、分かりやすい名前に変更しておきましょう。
  • ラベル (Label): 「ユーザーインターフェース」カテゴリの中にあります。これを画面上のボタンの下など、内容を表示したい場所に配置します。ラベルの初期テキストは空白にしておくと良いでしょう。

ブロックエディタでのプログラミング

残りのコンテンツを表示するにはログインする必要がありますどうぞ . 会員ではないですか ? 入会のご案内