Programming」カテゴリーアーカイブ

Clutter Box and Scroll

Clutter をチマチマやっているけど絶対値配置はやはり古い!
やはり BoxLayout で配置したい、スクロール機能も欲しい。

色々試して驚いた。
なんと Clutter はスクロールバーに相当するものが無い!

どうしても使いたいなら自分で作れってことでしょうか。
いや違う、そもそも Button Widget 相当すら無いではないか。
スマートフォンではいらないよね、そういうこと。

古臭い考え方をバッサリ捨てないと今の Mac と GNOME は使えない。
つか昔風に戻るなんてありえない、パソコン離れは加速する一方だし。

もしかしてスマートフォンのようなスクロールを想定しているのかも。
だったらやったろうジャン!

#!/usr/bin/gjs

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

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

    _init: function() {
        this.parent();
        let layout = new Clutter.BoxLayout({
            orientation: Clutter.Orientation.VERTICAL,
            spacing: 2
        });
        // ScrollActor
        this.scroll = new Clutter.ScrollActor({
            layout_manager: layout,
            scroll_mode: Clutter.ScrollMode.VERTICALLY,
            x_expand: true
        });
        // append
        for (let i=0; i<100; i++) {
            let m = new Clutter.Text({
                text: "TextLine: " + i,
                x_expand: true
            });
            m.set_background_color(Clutter.Color.new(255, 0, 0, 125));
            this.scroll.add_child(m);
        }
        // Scroll Action
        this.x_point = 0;
        this.y_point = 0;
        this.x_diff = 0;
        this.y_diff = 0;
        let gesture = new Clutter.GestureAction();
        gesture.connect("gesture-begin", Lang.bind(this, function(action, actor) {
            this.x_point = action.get_press_coords(0)[0] + this.x_diff;
            this.y_point = action.get_press_coords(0)[1] + this.y_diff;
            return true;
        }));
        gesture.connect("gesture-progress", Lang.bind(this, function(action, actor) {
            let x = action.get_motion_coords(0)[0];
            let y = action.get_motion_coords(0)[1];
            this.x_diff = this.x_point - x;
            this.y_diff = this.y_point - y
            let point = new Clutter.Point({
                x: this.x_diff,
                y: this.y_diff
            });
            this.scroll.scroll_to_point(point);
            return true;
        }));
        this.scroll.add_action(gesture);
        this.scroll.set_reactive(true);
        // this
        this.add_child(this.scroll);
        this.set_layout_manager(new Clutter.BoxLayout()); // Child Fill
        this.connect("hide", Clutter.main_quit);
        this.show_all();
    }
});

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

scroll_actor

ClutterBoxLayout を使うことで GtkBox 相当のようだ。
これで fill, expand を利用したレイアウトができる。
しかし ClutterActor に割り当てなのか、GTK+ と随分違うなと。

子 Actor を親サイズに広げたい時は Child Fill の部分のように。
これだけで GtkWindow を使うのとと同じ感覚になる。

スクロールは上記で上手くいった。
スマートフォンみたいにマウスで掴んで動かした分だけスクロール。
やろうと思えば iPhone のような慣性やポヨンを入れることもできる。
でもあの動きは特許だったような…

このスクロールをパソコンで使うかというと疑問もあるけれど。
コマンドが優秀な Linux もタブレットの時代が、ってワカンネーけど。
GNOME プロジェクトはガッツリ備えているということでしょう。

ClutterImage

今回は Clutter で画像をクルクル回してみよう。
3D ライブラリの定番ですね、ClutterImage を使うようだ。

ClutterImage: Clutter Reference Manual

画像ファイルをセットする方法は公式のサンプルコードがある。
コレを clutter_actor_set_content すればテクスチャになるってことかな。
画像の原寸と ClutterActor のサイズはどんな関係なんだろう。
チャチャッと実験コードを書いて試してみよう。

このサンプルを作っている時に気が付いたけど ClutterScore は deprecated だ。
CLutterTimeline に同様の property がある時点で気が付けよって感じですが。

#!/usr/bin/gjs

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

// Own Rewrite
const FILENAME = "/home/sasakima-nao/pic/game/gf/nae_yuki_ssr/ [ハロウィン13]優木苗.jpg";

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

    _init: function() {
        this.parent();
        // Image
        let image = new Clutter.Image();
        let pixbuf = GdkPixbuf.Pixbuf.new_from_file(FILENAME);
        let alpha = pixbuf.get_has_alpha() ? Cogl.PixelFormat.RGBA_8888 : Cogl.PixelFormat.RGB_888;
        image.set_data(
            pixbuf.get_pixels(),
            alpha,
            pixbuf.get_width(),
            pixbuf.get_height(),
            pixbuf.get_rowstride
        );
        // Rotate Actor
        this.actor = new Clutter.Actor();
        this.actor.set_pivot_point(0.5, 0.5);
        let xy = pixbuf.get_height() / pixbuf.get_width();
        this.actor.set_size(200, 200 * xy);
        this.actor.set_position(70, 50);
        this.actor.set_content(image);
        // Timer
        this.timeline = new Clutter.Timeline({
            duration: 100,
            loop: true
        });
        this.rotation = 0;
        this.timeline.connect("new-frame", Lang.bind(this, function() {
            this.rotation += 0.3
            this.actor.set_rotation_angle(Clutter.RotateAxis.Y_AXIS, this.rotation);
            this.actor.set_rotation_angle(Clutter.RotateAxis.Z_AXIS, this.rotation);
        }));
        this.timeline.start();
        // this
        this.add_child(this.actor);
        this.connect("hide", Clutter.main_quit);
        //this.set_user_resizable(true);
        this.show_all();
    }
});

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

clutter_image

公式サンプルの方法で画像全体のデータは取り込めるようだ。
ClutterActor の大きさに合わせてアスペクト比無視で拡縮される。
上記は一応大雑把にアスペクト比を合わせるようにしている。

PivotPoint や回転は全部 ClutterActor 側の仕事。
ClutterImage は ClutterActor の角度に合わせてレンダリングされる。
簡単すぎて拍子抜け、3D ってもっと面倒なイメージがあったのにな。

ClutterGestureAction

Clutter @ Mouse Drag | PaePoi
にてシークバーを motion-event で動かすようにした。

しかしこの方法は問題があった。
マウスカーソルが ClutterActor から外れるとシグナルが無効化される。
カーソルのキャプチャが必要だが手段が解らない。

いくら手段を探しても見つからない、もしかして無いのかも。
もう別のシーク方法を考えたほうが良さそうだ。

ClutterDragAction でなく ClutterGestureAction ならどうだ?
タッチパネル向けアクションだと思って無視していたけど調べてみよう。

ClutterGestureAction: Clutter Reference Manual

clutter_gesture_action_get_motion_coords
で普通に x, y の値が抜けるみたいだね。
問題はマウスカーソルが外れてもキャプチャするかどうかだ、実験。

#!/usr/bin/gjs

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

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

    _init: function() {
        this.parent();
        // var
        this.is_drag = false;
        // text
        this.text = new Clutter.Text();
        this.text.set_text("x = NULL; y = NULL");
        this.add_child(this.text);
        // Action
        let gesture = new Clutter.GestureAction();
        gesture.connect("gesture-begin", Lang.bind(this, function(action, actor) {
            let x = action.get_press_coords(0)[0] / this.width;
            let y = action.get_press_coords(0)[1] / this.height;
            this.text.set_text("x = " + x + "; y = " + y);
            return true;
        }));
        gesture.connect("gesture-progress", Lang.bind(this, function(action, actor) {
            let x = action.get_motion_coords(0)[0] / this.width;
            let y = action.get_motion_coords(0)[1] / this.height;
            this.text.set_text("x = " + x + "; y = " + y);
            return true;
        }));
        gesture.connect("gesture-end", Lang.bind(this, function(action, actor) {
            this.text.set_text("x = NULL; y = NULL");
        }));
        this.add_action(gesture);
        this.set_reactive(true);
        // this
        this.connect("hide", Clutter.main_quit);
        this.show_all();
    }
});

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

にてマウスをウインドウの外まで移動

clutter_gesture_action

しっかりマイナス値も取得できていますね。
ウインドウ外でマウスボタンを離すとしっかり NULL に戻る。

なんだよ、こんなに簡単な手段があったじゃないか。
昔の手段で思考停止していたら駄目だね、どんどん新しいことをやらないと。
ついでに。

this.player = new ClutterGst.Playback();
this.player.set_seek_flags(ClutterGst.SeekFlags.ACCURATE);

これだけでシークバーの追従がナイスになる。
思い通りに動かなかったものがキチンと動くようになっていくのはマジ楽しい。

ClutterGst Memory Leak

Y901x beta2 にトンデモなバグがあった。
ネットで拾った 2560×1440 の超デカイ動画を再生してみたら。

memory_leak

あっというまにメモリーリーク。
小さめのファイルでのテストだと気が付かなかったよ。

ファイルサイズより使用メモリのほうが大きいのでバッファではない。
Totem では問題無し、原因はドコだろう。

Clutter, ClutterGst 側のバグかと検索検索。
皆ビデオドライバーが原因ということで終わっている、違うみたい。

自分がやった処理を一つづつコメントアウトして何が悪いか地味に探す。
見つかった、細かい理由は解らないけどそういうことだったのかよ。

out_new_frame

ClutterGstPlayer の ‘new-frame’ シグナルを処理するとこうなるようだ。
void シグナルなので打つ手無し、つまりこのハンドラは使えない。

タイマーを使わずにイケたと思ったんだけーがなぁ。
素直にタイマー処理に、せっかくなので GLib でなく Clutter のを使う。

// Timer
let timeline = new Clutter.Timeline({duration:200});
timeline.connect("new-frame", Lang.bind(this, function() {
    // Timer Handler
}));
this.timer = new Clutter.Score({loop: true});
this.timer.append(null, timeline);
//
// etc...
//
this.timer.start();

これも ‘new-frame’ だがこっちは単なるタイマー。

ハンドラをこちらに移して再び再生。
Totem と同じメモリ使用量になった、なんとか解決。

もう少し弄って明日にでも beta3 に、完成は遠いかも。
ブログネタは Mac プログラミングと交互の予定が狂いまくり。

ClutterGst Aspect Rate

Y901x 1.2 beta2 を公開しました。

色々考えた結果リサイズはキーボード操作のみに。
だってメニューからリサイズしたことなんて作った本人は一度も無いモン!

GTK+ 3.20 でショートカットウィンドウが追加されるようなのでそれで賄う予定。
ソレを使う問題点は Ubuntu には完全未対応となるってことだけ。
Ubuntu GNOME でさえ 3.18 ですから。

その Ubuntu は次で独自 API に切り換えるという破滅への第一歩を選んだみたい。
GNOME が GNOME3 でアレだけシェアを落としたのを見てきたはずなのにねぇ。
とりあえず我がサイトは Ubuntu のページを全削除しておいた。

まあそれはどうでもよくて。
アスペクト比変更機能には本当に困った。

GStreamer-devel – Difference between videoscale and videobox

gst-launch-1.0 には aspect-ratio というオプションがある。
ならば普通に比率を与えれば有効にできる手段があるはず!
と探してみるけど全然見つからない。

手段は違うけどこんなのも見つけた。
kaa-candy/gstreamer.py at master ? freevo/kaa-candy ? GitHub
でも clutter_actor_animatev はもう使うなとのこと。

本当に Clutter はゴロッと変わって以前のサンプルコードが全滅状態。
初期段階で手を出さないでよかったなんて思うくらい。

結局自力でやることに。

        this.actor = new Clutter.Actor();
        this.actor.set_background_color(Clutter.Color.new(0, 0, 0, 255));
        //this.content = new ClutterGst.Aspectratio();
        this.content = new ClutterGst.Content();
        this.actor.set_content(this.content);
		//
		// etc...
		//
        this.set_video_size = function() {
            let allocation = this.vbox.get_allocation();
            let offset = SEEKBAR_HEIGHT;
            if (this.is_fullscreen)
                offset = 0;
            if (this.player.get_uri() != null) {
                let w = this.src_width;
                if (this.aspectrate == 1)
                    w = this.src_height *16 / 9;
                else if (this.aspectrate == 2)
                    w = this.src_height *4 / 3;
                let h = this.src_height;
                let aw = allocation.width;
                let ah = allocation.height - offset;
                let width, height, x, y = 0;
                if (aw * h > ah * w) {
                    width = w * ah / h;
                    height = ah;
                    x = (aw - width) / 2;
                    y = 0;
                } else {
                    width = aw;
                    height = h * aw / w;
                    x = 0;
                    y = (ah - height) / 2;
                }
                this.actor.set_width(width);
                this.actor.set_height(height);
                this.actor.set_position(x, y);
            } else {
                this.actor.set_width(allocation.width);
                this.actor.set_height(allocation.height - offset);
            }
        }

ま、自力変更は x の付かない Y901 でやっていたし。
ClutterGst.Content を適切にレイアウトするだけで難しくはない。

それより色々問題が出ている。

application_id を有効にすると多重起動の一つを閉じただけで落ちる。
GLXBadDrawable というエラーで落ちる。

GtkApplication からもう一つウインドウを開く処理を入れると落ちる。
GLXBadDrawable というエラーで落ちる。

GL* だから OpenGL だよね、Clutter 側がなんとかしてくれないとお手上げ。