Clutter @ Mouse Drag

Y901x beta は Clutter でシークバーを作っている。
最初は詰まりまくった、なんたって実際に利用するアプリに使うのは初めて。

普通に button-press-event を使ったら全然動作しなかったし。
実際 clutter_actor_set_reactive 関数を見つけるまで手段に超苦しむことに。

やはり ClutterCanvas で書き込もうとも考えた、昔ながらの方法。
でもそれ OpenGL である Clutter 上で Cairo を使うってことだ、変だよ。
しかも何故かエラー出まくり、cairo_t 引数が null になるしワケワカメ。

何か手段は無いかと試行錯誤してまさかの結論が出た。
そういえば Drag and Drop 操作はシークバーと同じ動作だと思いついた。
マウスで掴んで動かして離す、これはイケそう。

ClutterDragAction: Clutter Reference Manual

おぉ、ClutterAction にはこんなものがあるじゃないか。
コレでトラフ画像の ClutterActor を動かせばシークバーとして使えそう。
引数から X 値を得て横方向だけ動くようにすれば従来どおりの手段が使える。
と思ったけど…

#!/usr/bin/gjs

const Lang = imports.lang;
const Clutter = imports.gi.Clutter;

const ClWin = new Lang.Class({
    Name: 'ClWin',
    Extends: Clutter.Stage,

    _init: function() {
        this.parent();
        this.connect("hide", Lang.bind(this, function() {
            Clutter.main_quit();
        }));
        let actor = new Clutter.Actor();
        actor.set_background_color(Clutter.Color.new(255, 0, 0, 255));
        actor.set_size(50, 50);
        this.add_child(actor);
        // DnD
        let dgaction = new Clutter.DragAction();
        //dgaction.connect("drag-begin", Lang.bind(this, function(action, actor, event_x) {
        //    print(event_x);
        //}));
        dgaction.connect("drag-motion", Lang.bind(this, function(action, actor, delta_x) {
    		//actor.set_position(delta_x, 0);
            print(delta_x);
        }));
        //dgaction.connect("drag-end", Lang.bind(this, function(action, actor, event_x) {
        //    print(event_x);
        //}));
        actor.add_action(dgaction, true);
        actor.set_reactive(true);

        this.set_size(300, 150);
        this.show_all();
    }
});

Clutter.init(null);
new ClWin();
Clutter.main();

clutter_dnd

ハンドラ指定しただけで ClutterActor 自体がドラッグされてしまうジャン!
しかも動かすだけなら drag-motion のみ処理すればよかった。
これってそういう Action だったのNE!

更に引数の delta_x 値はドラッグされている ClutterActor 上の値だった。
つまり変化無し、これじゃシークバーなんかに使えないっつーの。
今回は無駄骨だったけどいつかこの経験が役に立つ、といいな。

だけど、コレをやったおかげで clutter_actor_set_reactive 関数を見つけた。
上記リンク先の Description に普通に書いてあったという。

this.is_drag = false;
let actor = new Clutter.Actor();
actor.connect("button-press-event", Lang.bind(this, function(actor, event) {
    this.is_drag = true;
    let [x, y] = event.get_coords();
    print("x= " + x + "; y= " + y);
}));
actor.set_reactive(true); // !!!

あら動いた、シグナルを有効にするにはやはり必須なのね。

motion-notify-event が無いのも困った。
motion-event だと Drag していなくても反応する、回避は簡単だが。

actor.connect("motion-event", Lang.bind(this, function(actor, event) {
    if (this.is_drag) {

これでなんとかシークバーに利用する用の値は得ることができる。

さて実際の実装方法だ。
ClutterActor は特定領域の塗り潰しができないっぽい、Canvas は使いたくない。
ならば別色の ClutterActor をもう一つ用意して被せれば、俺って天才だ。
せっかくだからデジタル表示も被せちゃえ、ClutterText は背景透過だし。

event.x を得て親の横幅で割った比率を被せた ClutterActor の横幅に適用。
という我ながら大雑把な手段なのに理想どおりに動いた。
しかもその値を ClutterGst にも適用できるというオマケ付きで。

それにしても GUI アプリを作るのって本当に楽しいですね。
思ったとおりに動いてくれた時の嬉しさが CUI の比じゃない。