タグ別アーカイブ: Clutter

ClutterGst Rotation

今時のスタンドアロンな動画プレイヤーには回転機能が必須だ。
なんたって回転編集していないスマホ動画を結構見かけるようになった。
昨年まではスマホ動画が主体になるなんて思いもしなかったのに。

ということで、ClutterGst で映像の回転を行う関数を探してみよう。

Clutter Gst 3.0.24 Reference Manual: Clutter Gst 3.0.24 Reference Manual

無いんカイ!

生 Gst を直で使うなら手段があった気がするんだがこれは困った。
いやまて、ClutterActor 自体を回転させればいいんでないの?
そのための OpenGL ES じゃないか、ということで実験コード。

#!/usr/bin/gjs

const Gtk = imports.gi.Gtk;
const Lang = imports.lang;
const Clutter = imports.gi.Clutter;
const ClutterGst = imports.gi.ClutterGst;
const GtkClutter = imports.gi.GtkClutter;

ClutterGst.init(null);

// width:640 height:800
const PATH = "/home/sasakima-nao/movie/GF/動く苗ちゃん(1).mp4";

const RotateWindow = new Lang.Class({
    Name: 'RotateWindow',
    Extends: Gtk.ApplicationWindow,

    _init: function(app) {
        this.parent({
            application: app
        });
        // player
        this.player = new ClutterGst.Playback();
        this.player.set_filename(PATH);
        this.content = new ClutterGst.Content();
        this.content.set_player(this.player);
        this.actor = new Clutter.Actor();
        this.actor.set_content(this.content);
        // embed
        let embed = new GtkClutter.Embed();
        embed.get_stage().add_child(this.actor);
        this.add(embed);
        // size
        this.resize(450, 450);
        this.actor.set_width(320);
        this.actor.set_height(400);
        this.actor.set_position(65, 25);
        // rotation
        this.actor.set_pivot_point(0.5, 0.5);
        this.actor.set_rotation_angle(Clutter.RotateAxis.Z_AXIS, 90);
        //
        this.show_all();
        this.player.set_playing(true);
    }
});

let app = new Gtk.Application();
app.connect("activate", function() {
    new RotateWindow(app);
});
app.run(null);

ピボットポイントを中心にして Z 軸で回せば映像も普通に回転するね。
リサイズ時には 90/270 度の時に縦横の値を入れ替えるだけでイケそうだ。
もっと正しい手段があるかもだけど筆者はコレでいいや。

あぁやっと完全放置だった Y901x を更新するネタができたぞい。
だって Youtube でオッサンが田舎道をバイクで走っているだけのばかり見ていたし。
ドラマやアニメより個人制作な素人動画のほうが妙に面白いよね。

Clutter Constraint

Clutter のドキュメントの中で Constraint がよく解らない。
Google 翻訳によると「制約」、っていったい何の制約なのだろう?
検索検索。

Clutter の Constraint (制約条件) – ふとしの日記

Gjs で動かないって new キーワードの場合引数はプロパティを JSON 指定ですし。
JSON 引数に書き換えたら普通に動いた、多分今は自身で解っていると思うけど。
五年前のこの当事はみんな手探り状態だったししかたがないね。

Clutter.SnapConstraint

えっとつまり。
Constraint ってアクションを他の Actor と連動させるって解釈でいいのかな?
この名前が不適切なのか、実は英語ではコレで合っているということなのかシラネ!

#!/usr/bin/gjs

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

const ConstraintWin = new Lang.Class({
    Name: 'ConstraintWin',
    Extends: Gtk.ApplicationWindow,

    _init: function(app) {
        this.parent({
            application: app
        });
        this.red = new Clutter.Actor({
            background_color: Clutter.Color.new(255, 0, 0, 255),
            x: 25,
            y: 25,
            width: 50,
            height: 50,
            reactive: true
        });
        this.blue = new Clutter.Actor({
            background_color: Clutter.Color.new(0, 0, 255, 255),
            width: 50,
            height: 50,
        });
        // DnD
        this.red.add_action(new Clutter.DragAction());
        // Constraint
        let bind = new Clutter.BindConstraint({
	        coordinate: Clutter.BindCoordinate.POSITION,
	        offset: 50,
            source: this.red
        });
        this.blue.add_constraint(bind);
        // Embed
        let embed = new GtkClutter.Embed();
        let stage = embed.get_stage();
        stage.add_child(this.red);
        stage.add_child(this.blue);
        this.add(embed);
        this.show_all();
    }
});

// init
GtkClutter.init(null);

let app = new Gtk.Application();
app.connect("activate", function() {
    new ConstraintWin(app);
});
app.run(null);

で赤いブロックをドラッグする。
うん、見事にオフセットされたまま連動してドラッグされるんだね。

ClutterAlignConstraint は Totem のボタン類みたいな用途で使えそう。

#!/usr/bin/gjs

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

const TotemLikeWin = new Lang.Class({
    Name: 'TotemLikeWin',
    Extends: Gtk.ApplicationWindow,

    _init: function(app) {
        this.parent({
            application: app
        });
        // Embed
        let embed = new GtkClutter.Embed();
        let stage = embed.get_stage();
        //
        this.red = new Clutter.Actor({
            background_color: Clutter.Color.new(255, 0, 0, 255),
            width: 200,
            height: 50,
            reactive: true
        });
        stage.add_child(this.red);
        // Constraint
        let x = new Clutter.AlignConstraint({
	        align_axis: Clutter.AlignAxis.X_AXIS,
	        factor: 0.5,
            source: stage
        });
        let y = new Clutter.AlignConstraint({
	        align_axis: Clutter.AlignAxis.Y_AXIS,
	        factor: 0.8,
            source: stage
        });
        this.red.add_constraint(x);
        this.red.add_constraint(y);
        //
        this.add(embed);
        this.show_all();
    }
});

// init
GtkClutter.init(null);

let app = new Gtk.Application();
app.connect("activate", function() {
    new TotemLikeWin(app);
});
app.run(null);

いくらリサイズしても常に同じ位置に貼り付ける動作がこんなにアッサリ!
多分こういうのが本来の使い方だと思う。

Clutter Animation

ClutterActor はアニメーション機能を内蔵している。

clutter_actor_save_easing_state # pause
clutter_actor_set_easing_duration # time
# move, resize, opacity, etc…
clutter_actor_restore_easing_state # start

たったコレだけで様々な変更がアニメーションになって動く。
ということでサンプルコード、クリック毎に Actor が入れ替わります。

#!/usr/bin/gjs

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

const AnimateWin = new Lang.Class({
    Name: 'AnimateWin',
    Extends: Gtk.ApplicationWindow,

    _init: function(app) {
        this.parent({
            application: app
        });
        this.red = new Clutter.Actor({
            background_color: Clutter.Color.new(255, 0, 0, 255),
            x: 25,
            y: 25,
            width: 50,
            height: 50
        });
        this.blue = new Clutter.Actor({
            background_color: Clutter.Color.new(0, 0, 255, 255),
            x: 75,
            y: 75,
            width: 50,
            height: 50
        });
        // action
        let click = new Clutter.ClickAction();
        click.connect("clicked", Lang.bind(this, function() {
            // pause
            this.red.save_easing_state();
            this.blue.save_easing_state();
            // animation time (default 250)
            this.red.set_easing_duration(2500);
            // move
            let [x, y] = this.red.get_position();
            let [x2, y2] = this.blue.get_position();
            this.red.set_position(x2, y2);
            this.blue.set_position(x, y);
            // play
            this.red.restore_easing_state();
            this.blue.restore_easing_state();
        }));
        // Embed
        let embed = new GtkClutter.Embed();
        let stage = embed.get_stage();
        stage.add_child(this.red);
        stage.add_child(this.blue);
        stage.add_action(click);
        this.add(embed);
        this.show_all();
    }
});

// init
GtkClutter.init(null);

let app = new Gtk.Application();
app.connect("activate", function() {
    new AnimateWin(app);
});
app.run(null);

コレだけだと使いどころがあんまりなさそうだよね。
しかし、ClutterImage の resize が超滑らかになるメリットがあった!
ということで comipoli に早速採用、永遠に実験用アプリ…

ClutterBoxLayout

ClutterStage が現在 Wayland で使えないのはあきらめて。
当面は GtkClutter を使ったサンプルコードを書いていこう。

今回は ClutterActor のレイアウトマネージャ。

GTK+ と全然違い property の layout-manager で指定するようだ。
デフォルトは ClutterFixedLayout になっている。
書くまでもなく絶対値配置なので子 Actor は重なって表示される。

GtkBox のように並べて配置するには ClutterBoxLayout を指定。
clutter_actor_add_child するだけで普通に並んでいく。

#!/usr/bin/gjs

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

const ListTest = new Lang.Class({
    Name: 'ListTest',
    Extends: Gtk.Window,

    _init: function() {
        this.parent({
            title: "add_actor"
        });
        // BoxLayout
        let layout = new Clutter.BoxLayout({
            orientation: Clutter.Orientation.VERTICAL,
            spacing: 1
        });
        // Actor
        this.actor = new Clutter.Actor({
            layout_manager: layout
        });
        // add
        let girls = ["椎名心実です", "あかね!", "くおえうえーーーうえうぅぅぅ"];
        for (let i=0; i<girls.length; i++) {
            let item = new Clutter.Text({
                x_align: Clutter.ActorAlign.START,
                x_expand: true,
                text: girls[i]
            });
            this.actor.add_child(item);
        }
        // Embed
        let embed = new GtkClutter.Embed();
        let stage = embed.get_stage();
        stage.add_child(this.actor);
        this.add(embed);
        // this
        this.connect("hide", Gtk.main_quit);
        this.show_all();
    }
});

GtkClutter.init(null);
new ListTest();
Gtk.main();

GListModel を使ってバインドもできる。
頻繁に入れ替えを行う場合はこちらのほうが便利かもしれない。

#!/usr/bin/gjs

const Clutter = imports.gi.Clutter;
const GtkClutter = imports.gi.GtkClutter;
const Gtk = imports.gi.Gtk;
const Gio = imports.gi.Gio;
const Lang = imports.lang;

const ListTest = new Lang.Class({
    Name: 'ListTest',
    Extends: Gtk.Window,

    _init: function() {
        this.parent({
            title: "GListStore"
        });
        // GListStore  
        this.model = new Gio.ListStore({
            item_type: Clutter.Text
        });
        // layout
        let layout = new Clutter.BoxLayout({
            orientation: Clutter.Orientation.VERTICAL,
            //pack_start: true,
            spacing: 1
        });
        // Actor
        this.actor = new Clutter.Actor({
            background_color: Clutter.Color.from_string("#aaa")[1],
            layout_manager: layout
        });
        this.actor.bind_model(this.model, Lang.bind(this, function(item) {
            return item;
        }));
        // append
        let girls = ["椎名心実です", "あかね!", "くおえうえーーーうえうぅぅぅ"];
        for (let i=0; i<girls.length; i++) {
            let item = new Clutter.Text({
                x_align: Clutter.ActorAlign.START,
                x_expand: true,
                text: girls[i]
            });
            this.model.append(item);
        }
        // remove
        this.model.remove(1);
        // Embed
        let embed = new GtkClutter.Embed();
        let stage = embed.get_stage();
        stage.add_child(this.actor);
        this.add(embed);
        // this
        this.connect("hide", Gtk.main_quit);
        this.resize(this.actor.width, this.actor.height);
        this.show_all();
    }
});

GtkClutter.init(null);
new ListTest();
Gtk.main();

GTK+ 同様に子 Actor のサイズによって親のサイズが拡大されるようだ。
ただし GTK+ 部品にまでは適用されないのでそこらは自力で。

ClutterStage on Wayland

GNOME 3.22 の Wayland 環境では ClutterStage で Window が作れない!

#!/usr/bin/env gjs

const Clutter = imports.gi.Clutter;

Clutter.init(null);
let stage = new Clutter.Stage({
    title: "TestWindow",
    background_color: Clutter.Color.from_string("#663322")[1]
});
stage.connect("hide", Clutter.main_quit);
stage.set_size(300, 200);
stage.show();
Clutter.main();

Window にならないヤン!

てか起動してもアクティブにならないし最初は見えないしetc…
アプリケーションメニューは普通に使えるし例外も吐かないんだけど。
Wayland はどういう認識をしているんだろう?

GNOME on Xorg でログインすれば普通に Window になる。
とはいえ通常なら GtkWindow に乗せて使うから致命的な問題ではないんだが。
サンプルコードを書くのに困るくらいだな。

ついでに GNOME on Xorg だと Gedit で Ctrl+F9 等のキーが使えるんだね。
これが Wayland だと動作しないのが筆者は微妙に不便。