Clutter の Effect を使う

今回は、ClutterEffect を使ってみます。ClutterActor の端っこがめくれ上がる ClutterPageTurnEffect です。残念ながら、まだ分かっていない部分もあるので、サンプルの説明はいい加減です。
はじめに C です。シグナル(イベント処理)については、また別の機会で詳しく説明するかもしれません。

#include <clutter/clutter.h>

static gboolean
enter_event (ClutterActor *actor, ClutterEvent *event, gpointer user_data)
{
    ClutterTransition *transition;
    
    transition = clutter_actor_get_transition (actor, "page_turn");
    clutter_transition_set_from (transition, G_TYPE_DOUBLE, 0.0);
    clutter_transition_set_to (transition, G_TYPE_DOUBLE, 0.5);
    clutter_timeline_rewind (CLUTTER_TIMELINE (transition));
    clutter_timeline_start (CLUTTER_TIMELINE (transition));
    printf ("enter_event\n");
    return CLUTTER_EVENT_STOP;
}

static gboolean
leave_event (ClutterActor *actor, ClutterEvent *event, gpointer user_data)
{
    ClutterTransition *transition;
    
    transition = clutter_actor_get_transition (actor, "page_turn");
    clutter_transition_set_from (transition, G_TYPE_DOUBLE, 0.5);
    clutter_transition_set_to (transition, G_TYPE_DOUBLE, 0.0);
    clutter_timeline_rewind (CLUTTER_TIMELINE (transition));
    clutter_timeline_start (CLUTTER_TIMELINE (transition));
    printf ("leave_event\n");
    return CLUTTER_EVENT_STOP;
}

int
main (int argc, char *argv[])
{
    ClutterActor *stage, *actor;
    ClutterEffect *page_turn;
    ClutterTransition *transition;
    ClutterInterval *interval;
    
    if (clutter_init (&argc, &argv) != CLUTTER_INIT_SUCCESS)
        return 1;
    stage = clutter_stage_new ();
    g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL);

    actor = clutter_actor_new ();
    clutter_actor_set_background_color (actor, clutter_color_get_static (CLUTTER_COLOR_RED));
    clutter_actor_set_reactive (actor, TRUE);
    clutter_actor_set_size (actor, 100, 100);
    clutter_actor_set_position (actor, 100, 100);
    g_signal_connect (actor, "enter-event", G_CALLBACK (enter_event), NULL);
    g_signal_connect (actor, "leave-event", G_CALLBACK (leave_event), NULL);
    
    page_turn = clutter_page_turn_effect_new (0.0, 60.0, 10.0);
    clutter_actor_add_effect_with_name (actor, "page_turn", page_turn);

    transition = clutter_property_transition_new ("@effects.page_turn.period");
    clutter_timeline_set_duration (CLUTTER_TIMELINE (transition), 250);
    clutter_actor_add_transition (actor, "page_turn", transition);
    g_object_unref (transition);

    clutter_actor_add_child (stage, actor);
    clutter_actor_show (stage);
    clutter_main ();
    return 0;
}

マウスカーソルが ClutterActor に入る時と出る時に、enter_event() と leave_event() が呼ばれます。それらの中身を説明する前に、main() を見ていきます。最初は飛ばして、、、

clutter_actor_set_reactive (actor, TRUE);

この行で、ClutterActor がシグナルに反応するようになります。ちょっと飛んで、、、

g_signal_connect (actor, "enter-event", G_CALLBACK (enter_event), NULL);
g_signal_connect (actor, "leave-event", G_CALLBACK (leave_event), NULL);

この2行で、マウスの enter-event と leave-event が発生するたびに、それぞれ enter_event() と leave_event() を呼び出すようにします。

page_turn = clutter_page_turn_effect_new (0.0, 60.0, 10.0);
clutter_actor_add_effect_with_name (actor, "page_turn", page_turn);

この2行で、 page_turn という ClutterPageTurnEffectを作成し、"page_turn"という名前で actor に追加します。

transition = clutter_property_transition_new ("@effects.page_turn.period");
clutter_timeline_set_duration (CLUTTER_TIMELINE (transition), 250);
clutter_actor_add_transition (actor, "page_turn", transition);
g_object_unref (transition);

そして、ここがよくわからないところ。ClutterTransition は ClutterActor に追加の機能を与えるものなのですが、作成時に property-name が必要になります。今回は ClutterActor に ClutterEffect を追加して、名前が page_turn で ClutterPageTurnEffect の period プロパティを変更するので、 "@effects.page_turn.periog" で作成するのですが、命名方法がわかっていません。
しかし、これで動くのでよしとします。
名前付きで作成し、アニメーション時間を 250 ms に設定し、ClutterActor に ClutterTransition を追加しています。devhelp にあるように、最後は g_object_unref() します。

こうしておいて、コールバック関数では、以下のようにします。

transition = clutter_actor_get_transition (actor, "page_turn");
clutter_transition_set_from (transition, G_TYPE_DOUBLE, 0.0);
clutter_transition_set_to (transition, G_TYPE_DOUBLE, 0.5);
clutter_timeline_rewind (CLUTTER_TIMELINE (transition));
clutter_timeline_start (CLUTTER_TIMELINE (transition));

ClutterActor の ClutterTransition を名前で呼び出し、period の最初と最後の値を設定し、タイマーを巻き戻し、タイマーをスタートします。 period はページをめくる量で、0.0はめくっていない 1.0 が完全にめくっている状態です。
こんな感じで、ページをめくるサンプルが完成です。devhelp の ClutterDragAction にサンプルがあるので、そっちを見たほうが良いかもしれません。

次に Gjs です。

#! /usr/bin/env gjs
const Clutter = imports.gi.Clutter;
const GObject = imports.gi.GObject;

function enter_event(actor, event, user_data) {
    let transition = actor.get_transition("page_turn");
    let interval = transition.get_interval();
    interval.set_initial(0.0);
    interval.set_final(0.5);
    transition.rewind();
    transition.start();
}

function leave_event(actor, event, user_data) {
    let transition = actor.get_transition("page_turn");
    let interval = transition.get_interval();
    interval.set_initial(0.5);
    interval.set_final(0.0);
    transition.rewind();
    transition.start();
}

Clutter.init(null);
let stage = new Clutter.Stage();
stage.connect('destroy', Clutter.main_quit);

let actor = new Clutter.Actor();
actor.set_background_color(Clutter.Color.get_static (Clutter.StaticColor.RED));
actor.set_reactive(true);
actor.set_size(100, 100);
actor.set_position(100, 100);
actor.connect('enter-event', enter_event);
actor.connect('leave-event', leave_event);
    
let page_turn = new Clutter.PageTurnEffect({period:0.0, angle:60.0, radius:10.0});
actor.add_effect_with_name("page_turn", page_turn);

let transition = new Clutter.PropertyTransition({property_name:"@effects.page_turn.period"});
transition.set_duration(250);
actor.add_transition("page_turn", transition);

let interval = new Clutter.Interval({value_type:GObject.TYPE_DOUBLE});
transition.set_interval(interval);

stage.add_child(actor);
stage.show();
Clutter.main();

基本は変わりませんが、ClutterTransition の set_from() と set_to() メソッドが使えなかったので、 ClutterTransition の最初と最後の値を保持する ClutterInterval を利用して、値を設定します。実はこの方法もよくわからなかったので、BUG だと投げて叱られた経緯があります。。。https://bugzilla.gnome.org/show_bug.cgi?id=683952

ClutterTransition ではなく ClutterInterval を使うことだけ理解できれば説明は不要ですね。