アニメーション

要約:
  • アニメーションは変化と動きをグラフィック ディスプレイに追加します。
  • アニメーションを制御するには、Visual.animate メソッドを使用して Timer を作成します。
  • Timers は、コードを指定された間隔または頻度で実行する必要がある場合に役に立ちます。
アニメーションは変化と動きをグラフィック ディスプレイに追加します。「テキスト プロシージャにおけるアニメーションの使用」のセクションでは、簡単なアニメーションを使ってテキストの色を変える方法を示しています。この章ではアニメーションについてさらに詳しく説明します。
2D と 3D コンテキストの両方にアニメーションを使用できます。Scene における 3D アニメーションの説明と例については、「Scene オブジェクトのインタラクション」および「SceneObjectController およびアニメーション」を参照してください。

animate メソッドの使用

要約:
  • interval および frequency プロパティを使用してアニメーションを制御します。
  • アニメーションの開始および停止されるには、repeat プロパティを使用します。
  • アニメーションの開始を遅らせるには、after マクロを使用します。
Curl® 言語でのアニメーションは非常にシンプルです。Timer の設定には、Visual.animate メソッドを使用します。Timer は、それ自体で TimerEvent を発生させてコード実行を制御します。Timer に与えるイベント ハンドラのコードにより、グラフィックの外観を変更してタイマー イベントに応答します。Curl 言語は、Timer 管理の下位レベルの詳細を扱います。
interval または frequency のいずれかを指定して、発生するアニメーションの速度を制御します。次の例では、アニメーションは四角形で発生する一連の色の変化で構成されています。この変化のタイミングは interval 引数の値によって調整されています。

例: 簡単なアニメーションによる色の変更
{let r:Fraction, g:Fraction, b:Fraction}
{let f:Fill = {Fill width=1cm, height=1cm, background="black"}}
{let cb:CommandButton =
    {CommandButton
        label = "change color for 5.5 seconds...",
        {on Action do
            {f.animate
                interval=0.1s,
                repeat = 55,
                {on TimerEvent do
                    set r = (r + 0.05) mod 1.0
                    set g = (g + 0.02) mod 1.0
                    set b = (b + 0.03) mod 1.0
                    set f.background = {Color.from-rgb r, g, b}
                }
            }
        }
    }
}
{spaced-vbox f, cb}
Curl 言語で可能な範囲でイベント ハンドラの Visual を変更することができます。次の例では、四角形のサイズが色とともに変わります。

例: サイズと色の変更
{let r:Fraction, g:Fraction, b:Fraction}
{let f:Fill =
    {Fill width = 1cm,
        height = 1cm,
        background = "black"}
}
{let cb:CommandButton =
    {CommandButton
        label = "change size and color for 5.5 seconds...",
        {on Action do
            {f.animate
                interval = 0.1s,
                repeat = 55,
                {on TimerEvent do
                    set r = (r + 0.05) mod 1.0
                    set g = (g + 0.02) mod 1.0
                    set b = (b + 0.03) mod 1.0
                    set f.background = {Color.from-rgb r, g, b}
                    set f.width = 4cm * r
                    set f.height = 4cm * g
                }
            }
        }
    }
}
{VBox
    {HBox height = 5cm, width = 5cm, f},
    {HBox cb}
}
interval または frequency 属性のいずれかを指定する必要があることに注意してください。両方とも省略または指定した場合、エラーが発生します。次の例では、アニメーションのタイミングの制御のために intervalfrequency に置き換えています。

例: タイミング制御に frequency を使用
{let r:Fraction, g:Fraction, b:Fraction}
{let f:Fill = {Fill width=1cm, height=1cm, background="black"}}
{let cb:CommandButton =
    {CommandButton
        label = "change color for 5.5 seconds...",
        {on Action do
            {f.animate
                frequency=10Hz,
                repeat = 55,
                {on TimerEvent do
                    set r = (r + 0.05) mod 1.0
                    set g = (g + 0.02) mod 1.0
                    set b = (b + 0.03) mod 1.0
                    set f.background = {Color.from-rgb r, g, b}
                }
            }
        }
    }
}
{spaced-vbox f, cb}

タイマーの維持

animate メソッドは Timer を返します。この Timer を変数に格納すると、Timer の作成後にこれを操作することができます。タイマーがアクティブなときに Timer の任意のプロパティを変更すると、変更後もアクティブである場合は Timer の停止や新しい値での開始などの効果を得ることができます。
次の例では、ユーザーは Timer のコマンド ボタンを使用してアニメーションの速度を変更することができます。タイマーの間隔が短すぎると、ユーザーが間隔を増やすまで "Faster" ボタンは無効になっています。

例: 後で使用できるように Timer を格納
{let r:Fraction, g:Fraction, b:Fraction}
{let f:Fill = {Fill width=1cm, height=1cm, background="black"}}
{let t:Timer =
    {f.animate
        interval=0.1s,
        repeat = 0,
        {on TimerEvent do
            set r = (r + 0.05) mod 1.0
            set g = (g + 0.02) mod 1.0
            set b = (b + 0.03) mod 1.0
            set f.background = {Color.from-rgb r, g, b}
        }
    }
}
{let run-button:CommandButton =
    {CommandButton label = "Start",
        {on Action do
            set t.repeat = 1000
        }
    }
}
{let stop-button:CommandButton =
    {CommandButton label = "Stop",
        {on Action do
            set t.repeat = 0
        }
    }
}
{let faster-button:CommandButton =
    {CommandButton
        label="Faster",
        {on Action do
            {if t.interval > 0.03s
             then
                set t.interval = t.interval - 0.02s
             else
                set faster-button.enabled? = false
            }
        }
    }
}
{let slower-button:CommandButton =
    {CommandButton
        label="Slower",
        {on Action at slower-button:CommandButton do
            set t.interval = t.interval + 0.02s
            {if t.interval > 0.03s and not faster-button.enabled?
             then
                set faster-button.enabled? = true
            }
        }
    }
}
{HBox
    run-button,
    stop-button,
    faster-button,
    slower-button,
    {value f}
}
Timer を作成してから、このプロパティのいくつかを設定できます。特に重要なプロパティは repeat で、Timer イベントが停止するまでに発生する回数を指定します。repeat が 0 に設定されている場合、アニメーションは起こりません。-1 に設定されている場合は、アニメーションは永久に続きます。既定値は -1 です。次の例では repeat を使用して、一度に 1 フレームまたは 10 フレームの速さでアニメーションを進めます。

例: repeat 引数の使用
{let r:Fraction, g:Fraction, b:Fraction}
{let f:Fill = {Fill width=1cm, height=1cm, background="black"}}
{let t:Timer =
    {f.animate
        interval=0.1s,
        repeat=10,
        {on TimerEvent do
            set r = (r + 0.05) mod 1.0
            set g = (g + 0.02) mod 1.0
            set b = (b + 0.03) mod 1.0
            set f.background = {Color.from-rgb r, g, b}
        }
    }
}
{HBox
    {CommandButton
        label="Ten More Frames",
        {on Action do
            set t.repeat = 10
        }
    },
    {CommandButton
        label="Step (1 more frame)",
        {on Action do
            set t.repeat = 1
        }
    },
    {value f}
}
アニメーションの開始や停止には、repeat プロパティを使用できます。次に例を示します。

例: アニメーションの開始または停止で repeat を使用
{let r:Fraction, g:Fraction, b:Fraction}
{let f:Fill = {Fill width=1cm, height=1cm, background="black"}}
{let t:Timer =
    {f.animate
        repeat = 0,
        {on TimerEvent do
            set r = (r + 0.05) mod 1.0
            set g = (g + 0.02) mod 1.0
            set b = (b + 0.03) mod 1.0
            set f.background = {Color.from-rgb r, g, b}
        },
        interval=0.1s
    }
}
{HBox
    {CommandButton
        label="Start",
        {on Action do
            set t.repeat = -1
        }
    },
    {CommandButton
        label="Stop",
        {on Action do
            set t.repeat = 0
        }
    },
    {value f}
}

after の使用

after 式を使用して、アニメーションが始まる前に一定の時間待機させておくことができます。次に例を 2 つ示します。

例: after を使用してアニメーションを遅らせる方法
{let r:Fraction, g:Fraction, b:Fraction}
{let f:Fill = {Fill width=1cm, height=1cm, background="black"}}
{let t:Timer =
    {f.animate
        repeat = 0,
        {on TimerEvent do
            set r = (r + 0.05) mod 1.0
            set g = (g + 0.02) mod 1.0
            set b = (b + 0.03) mod 1.0
            set f.background = {Color.from-rgb r, g, b}
        },
        interval=0.1s
    }
}
{HBox
    {CommandButton
        label="Start in 5 seconds...",
        {on Action do
            {after 5s do
                set t.repeat = -1
            }
        }
    },
    {CommandButton
        label="Stop right now!",
        {on Action do
            set t.repeat = 0
        }
    },
    {value f}
}

例: after を使用してアニメーションを遅らせる別の方法
{let r:Fraction, g:Fraction, b:Fraction}
{let f:Fill = {Fill width=1cm, height=1cm, background="black"}}
{let t:Timer = {f.animate
                   interval=0.1s,
                   repeat=0,
                   {on TimerEvent do
                       {after 5s do
                           set r = (r + 0.05) mod 1.0
                           set g = (g + 0.02) mod 1.0
                           set b = (b + 0.03) mod 1.0
                           set f.background = {Color.from-rgb r, g, b}
                       }
                   }
               }
}
{HBox
    {CommandButton
        label="Start in 5 seconds...",
        {on Action do
            set t.repeat = -1
        }
    },
    {CommandButton
        label="Stop in 5 seconds...",
        {on Action do
            set t.repeat = 0
        }
    },
    {value f}
}

タイマー

要約:
  • Timer はそれ自体で TimerEvent を発生させて、コード実行のタイミングを制御します。
  • Visual.animate から Timer を取得した場合、Curl 言語が適当な時点で、その有効および無効の設定などの指定事項を処理します。
  • Timer クラスを直接インスタンス化して Timer を作成した場合、タイマーの有効または無効はユーザーが設定する必要があります。
Timer はコード実行のタイミングを制御し、アニメーション製作には必須のツールです。Timer はまた、指定の間隔または頻度でコードを実行する必要がある任意の状況で役に立つツールです。「値を持たない新しいコントロール」のセクションでは、Timer を使用して clock コントロールの時間表示を更新する例を示しています。
各タイマーは Timer クラスによって表され、格納されているタイミング情報を使用してそれ自体で TimerEvent を発生させます。TimerEvent クラスは、Timer オブジェクトで発生するタイマー イベントすべてを表します。Timer はグラフィック オブジェクトではありません。したがってグラフィック階層を上に向かってイベントを発生し続けるグラフィック オブジェクトとは異なり、作成した場所には関係なくタイマー自身で TimerEvent を発生させます。

タイマーの作成および起動

Visual.animate から Timer を取得する場合、引数として Timer のプロパティの値を animate に与えます。Timer クラスを直接インスタンス化して Timer を作成する場合、クラス コンストラクタが Timer のプロパティを設定します。
次のリストにこれらのプロパティを示します。
各プロパティの詳細については、『API リファレンス』を参照してください。
Timer は、アクティブである限り発生します。アクティブ タイマーの状態は次のとおりです。
アクティブ タイマーは、そのプロパティ設定に従ってタイマー イベントを発生させます。タイマーの Timer.enabled? プロパティを false に設定するか、Timer.repeat プロパティを 0 に設定するとタイマーがインアクティブになります。Timer がアクティブな場合は、アクセッサ Timer.active? は true を返します。
注意: Visual.animate から Timer を取得した場合は、Visual で発生した AttachEvent によりTimer が有効になります。したがって、このような場合は repeat プロパティを 0 に設定してアニメーションを停止するようにしてください。Timer.enabled? を false に設定するだけでは不十分です。その理由は、関連付けられている Visual で別の AttachEvent が発生すると、タイマーがすぐに有効になるからです。

タイマーのインアクティブ化

TimerVisual.animate を使用せずに直接取得する場合、タイマーのアクティブ化とインアクティブ化を考慮に入れる必要があります。1 つには、アプレット、Curl 言語プロセス、およびスレッドに関連する Timer の寿命です。
タイマーを明示的にインアクティブにしない場合、Curl アプレットが存続する限りタイマーも存在します。アプレットが消滅すると、関連する Curl 言語プロセス、関連するスレッドも消滅し、アクティブ タイマーのリストが消えます。アプレットがブラウザ内またはブラウザの履歴にある限り存続することに注意してください。
ブラウザ内ではなくブラウザ履歴でアプレットが存続している場合、一時停止の状態にあると言えます。一時停止しているアプレットの状態は次のとおりです。
アプレットが一時停止されているときは、アクティブなモーダル ダイアログは自動的にキャンセルされます。
アブレットが消滅するまでタイマーを発生させ続けるより、必要がなくなれば Timer をインアクティブにすることをお勧めします。たとえば、ダイアログまたはアプレット領域の外側のビューで Timer を作成すると、ビューを閉じても Timer はインアクティブになりません。実際には、新しい View を繰り返し作成した場合、複数の Timer が作成されることになり、アプレットが消滅するまでこれらのタイマーはアクティブのままになります。
TimerVisual に関連づけられている場合、DetachEvent のイベント ハンドラを Visual に追加できます。このイベント ハンドラは Timer を無効にできます。VisualDetachEvent を取得したときに、関連する Timer を無効するのがいいタイミングです。その理由は、この時点ではオブジェクトがグラフィック階層にはない、つまり表示されていないからです。
AttachEvent のイベント ハンドラを追加することもできます。VisualAttachEvent を取得したときに、関連する Timer を再度有効にすることができます。その理由は、この時点でオブジェクトがグラフィック階層にある、つまり表示されているからです。
注意:animate メソッドの使用」で述べられているように Visual.animate を使用して Timer を作成した場合、Timers の有効および無効の設定は内部で処理されます。

タイマー イベント

次の例は、次のプロパティを設定したタイマーを示します。
この例では、Timer に関連付けられた CommandButton を作成します。ユーザーがボタンをクリックすると、ボタンの幅が 1 秒間隔で 3 回変わります。

例: Timer イベント
{CommandButton
    width=5cm,
    height=2.5cm,
    label="Click and watch this space",
    margin=5mm,
    || When the user clicks on it ...
    {on Action at cb:CommandButton do
        || A timer is created that fires 3 times, at 1-second intervals
        let t:Timer =
            {Timer
                interval=1s,
                repeat=3,
                || When the timer gets the TimerEvent ...
                {on TimerEvent do
                    || the button's size is changed
                    set cb.width = cb.width + 1cm
                }
            }
    }
}
一度だけ発生するイベントの場合は、「after の使用」で説明されている after 式を使用します。
タイマーは、マシンによってはナノ秒に近い単位まで正確に時間を測定します。ただし、タイマーが開始したときにコンピュータが別のイベント処理で忙しい場合は、TimerEvent 処理がしばらく遅れることがあります。
注意: このような遅延のため、Timer を使用してそれ自体で時間を測定することは推奨されません。TimerEvent 間の正確な時間が必要な場合は、TimerEvent 内の DateTime クラスを使用して経過時間を取得することを推奨します。

パフォーマンスに関する問題

タイマーのインアクティブ化」で説明されているように、必要がなくなってもまだアクティブ状態の Timers は、システムのパフォーマンスを遅くする可能性があります。
アニメーションがプロセッサ時間を消費し、ドキュメントのスクロールや配置が遅くなる可能性があるため、アニメーションを含むアプレットはタイマーを無効にしてロードすることを推奨します。
簡単なアニメーションで計算があまり起こらないような場合は、安定したフレーム率で実行され残りのドキュメントも対応時間が遅くなりません。

イベント ループ

発生したイベントは、一般的なイベント用の標準イベント キュー、タイマー イベント用のタイマー イベント キューに置かれます。
イベント ループにはイベント処理の順序が定義されています。イベント ループの各ラウンドには、2 つの段階があります。
  1. イベント ループの各ラウンドの第 1 段階では、現在イベント キューにある一般イベントがすべて順番に処理されます。
    これらのイベントが処理されている間、新しいイベントが入ってくることがあります。
    • 新しいタイマー イベントは、発生の予定時間とは無関係にタイマー キューに蓄積されます。
    • 新しい一般イベントはイベント キューの最後に追加され、イベント ループの次のラウンドのイベント段階で処理されることになります。
  2. イベント ループの各ラウンドの第 2 段階では、現在の時刻で発生するようスケジュールされているタイマー イベントすべてが発生します。
    タイマー イベントの発生中に新しいイベントが入って来ることがあります。新しいイベントは適切なキューに追加されますが、イベント ループのこのラウンドでは発生されません。