Clutter」タグアーカイブ

Clutter Fullscreen HeaderBar

フルスクリーン時にマウスカーソルを上に持って行くとヘッダーがヒョッコリ。
という処理は GTK+ なら GtkOverlay で簡単に実現できる。

GTK+ Overlay | PaePoi

けれど Y901x でやってみたらどうやっても表示してくれない。
GtkOverlay は GtkClutter.Embed の上には被せることができないようだ。

なので GtkClutter.Actor の上にヘッダーを置いて表示させるしかない。
GTK+ の上に Clutter を載せて、その上に GTK+ を置く。
という奇妙な構造だけど他に手段が無い。
下記はそのまま動くようにしたものを抜き出ししてみた。

#!/usr/bin/gjs

const GObject = imports.gi.GObject;
const Gtk = imports.gi.Gtk;
const Clutter = imports.gi.Clutter;
const GtkClutter = imports.gi.GtkClutter;

var ClWindow = GObject.registerClass({
    GTypeName: 'ClWindow'
}, class ClWindow extends Gtk.Window {
    _init() {
        super._init();
        this.is_fullscreen = false;
        // actor
        this.actor = new Clutter.Actor();
        // Fullscreen Header and Actor
        this.upperbar = new Gtk.HeaderBar({
            no_show_all: true,
            valign: Gtk.Align.START
        });
        this.upperActor = new GtkClutter.Actor({
            contents: this.upperbar
        });
        this.upperActor.hide();
        // Restore Button
        let restoreButton = new Gtk.Button({
            image: Gtk.Image.new_from_icon_name('view-restore-symbolic', Gtk.IconSize.MENU),
            visible: true
        });
        restoreButton.connect('clicked', ()=> {
            this.change_fullscreen();
        });
        this.upperbar.pack_end(restoreButton);
        // Embed
        let embed = new GtkClutter.Embed();
        let stage = embed.get_stage();
        stage.add_child(this.actor);
        stage.add_child(this.upperActor);
        stage.connect('motion-event', (actor, event)=> {
            if (this.is_fullscreen) {
                let [, y] = event.get_coords();
                // upperbar
                if (this.is_fullscreen) {
                    if (y <= this.hb_height+10 && !this.upperbar.visible) {
                        this.upperActor.show();
                        this.upperbar.show();
                    } else if (y > this.hb_height+10 && this.upperbar.visible) {
                        this.upperActor.hide();
                        this.upperbar.hide();
                    }
                }
            }
        });
        // box
        let vbox = new Gtk.Box({orientation: Gtk.Orientation.VERTICAL});
        vbox.pack_start(embed, true, true, 0);
        vbox.connect('size-allocate', (vbox, allocation)=> {
            if (this.is_fullscreen) {
                this.upperActor.set_size(allocation.width, this.hb_height);
                this.upperbar.set_size_request(allocation.width, this.hb_height);
            }
        });
        // Double Click Fullscreen
        vbox.connect('button-press-event', (widget, event)=> {
            if (event.get_event_type() == 5) {
                this.change_fullscreen();
            }
            return true;
        });
        // main
        let hb = new Gtk.HeaderBar({show_close_button: true});
        this.set_titlebar(hb);
        this.add(vbox);
        this.connect('delete-event', ()=> {Gtk.main_quit();});
        this.resize(300, 300);
        this.show_all();
        this.hb_height = hb.get_allocated_height();
    }
    change_fullscreen() {
        this.is_fullscreen = this.is_fullscreen === false;
        if (this.is_fullscreen) {
            this.fullscreen();
        } else {
            this.unfullscreen();
            if (this.upperbar.visible) {
                this.upperbar.hide();
                this.upperActor.hide();
            }
        }
    }
});
Gtk.init(null);
GtkClutter.init(null);
let w = new ClWindow();
Gtk.main();

やっぱり長いな。

細かい解説はしないから自力で解析してね。
注意点は GtkWindow の motion-notify-event で処理しないこと。
ドロップしたメニューを選ぼうとマウスを動かすとメニューが消える罠がwwwww

って Comipoli 0.3.5 がそうなっているということに今気が付いた!
Comipoli は全部 GTK+ なんだが、同じ手段は使えない。
さて困った、eog みたいにメニューを出さないという手もあるけど。

Fedora 30 Upgrade

Fedora 30 来ました。
下は [ダウンロード] ボタンを押した状態、今回はアップグレードです。

UnitedRPMs リポジトリを一時的に Disable にしないと更新できないエラーが出る。
無効にして再開、[インストール] ボタンが出るので押してパスワード。
しばらく待っていると Fedora 30 になっていた、アップグレードおしまい。

見た目の変更点とかはインストールだけなブロガーが書くだろうから無視して。
ずっと使ってプログラミングしている人しか解らない変更点なんかを。
てか、Linux ってプログラミングしない人には不便なだけの OS なんだけど。

何より、Gedit てか GtkSourceView が改善された!
タブのアクティブ毎にカーソル位置へ勝手にスクロールするのがウザかった。
それが何か打ち込むまで勝手にスクロールをしなくなった、マジで嬉しい。

Gedit のタブをドラッグして別ウインドウ時のアニメーションが復活。
前に戻っただけじゃんというのは置いておいて。
Nautilus は何故コレをできなくしてしまったのだろう?

Nautilus でファイルのドラッグ移動が失敗する場合があるのは変わっていないな。
ただ筆者はドラッグがスゲェやり辛い Macbook Air 2018 を併用しているということで。
command+X, command+V で移動が普通になっているので Ctrl+X でのファイル移動に苦は無い。

デフォルトじゃないけど Geary(メーラー) の起動が死ぬほど遅くなった。
追記: アップデートで普通になった
何故だろう、思いっきり GTK3 アプリなのに。
Google Chrome, KeepassXC, GNOME MPV 等は問題無し。

一番気になるアプリケーションメニューの廃止。
ハンバーガーメニューにする前の Comipoli 0.3.4 を起動してみる。

ココに出るだけか、あわてて作り変える必要は無かったな。

GNOME アプリは元の位置に「新しいウインドウを開く」が出る。
我がアプリではココが空になる、どうやるか後で調べる必要があるな。
「詳細を表示する」を選ぶと gnome-softwere の該当ページが開く、これ必要か?
ちなみに我がアプリ等の自前インストールは「見つかりません」と出る。

設定のアプリケーションって何かと開いたら我がアプリまで。
*.desktop ファイルを解析している、というか *.desktop を編集できるってか。
追加ができないのではあまり意味は無いと思うんですけど。

後は特に変わったような気がしない、何か気が付いたら又。

そうそう、我がアプリの Comipoli はハンバーガーメニュー化だけで問題無し。
Y901x は引数付き起動ができなくなっていた!
Gjs, Clutter, GStreamer のドレが原因か解らないけどなんとかしなきゃ。

二時間くらい調べてなんとか原因が判明。
ClutterGstVideoSink で得る GstPad が null になっている。
別の手段で動画のオリジナルサイズを得る必要があるなと。

ClutterGstTypes: Clutter Gst 3.0.24 Reference Manual

ClutterGstVideoResolution 構造体に width, height があるな。
だったらこうすればサイズが得られるのかも?

var Y901xWindow = GObject.registerClass({
    GTypeName: 'Y901xWindow'
}, class Y901xWindow extends Gtk.ApplicationWindow {
    _init(app) {
        super._init({application: app});
        // etc...
        this.player = new ClutterGst.Playback();
        // Ready
        this.player.connect('ready', (player)=> {
            this.player.set_playing(false);
            // Get Origin Size
            let vsink = player.get_video_sink();
            /* old
            let it = vsink.iterate_pads();
            it.foreach((pad)=> {
                let caps = pad.get_current_caps();
                let struct = caps.get_structure(0);
                this.src_width = struct.get_int('width')[1];
                this.src_height = struct.get_int('height')[1];
            });*/
            // new (Fedora 30)
            let frame = vsink.get_frame();
            this.src_width = frame.resolution.width;
            this.src_height = frame.resolution.height;

イケた!

ということで Comipoli 0.3.5, Y901x 1.2.1 公開。
Linux アプリケーション – L’Isola di Niente

Y901x のほうは変更点が多いけど解説すると長くなるのでまた今度。

ところで Fedora 29 の時に勘違いしていたけど GTK+ の 2 年枚更新は前回だったのね。
だから少し仕様が変わっていたのか、次の仕様変更は Fedora 33 なのね。
筆者はそれに合わせてクリーンインストールする予定。

screentone 2D and 3D

コミックブックアーカイブビューアを作っている筆者でありますが。
スクリーントーンの描写が変になる場合があることに困っていた。

Clutter(OpenGL ES) でリサイズしているのが悪いのかな?
と思い GdkPixbuf をリサイズしてからレンダリングで解決した。
よかったよかった、じゃない!もう少し調べよう。

Jトーン 網点(ドットパターン) J-00|スクリーントーン 通販【ジェイトーン・ネットショッピング】

トーンは上記のサンプル画像をお借りしてと。

#!/usr/bin/env python3

import gi
gi.require_version("Gtk", "3.0")
gi.require_version("Clutter", "1.0")
gi.require_version("GtkClutter", "1.0")
from gi.repository import Gtk, Gdk, GdkPixbuf, Clutter, GtkClutter, Cogl

class DrawWindow(Gtk.Window):
    """
        2D and 3D View Check
    """
    def __init__(self):
        Gtk.Window.__init__(self)
        # tone
        self.pixbuf = GdkPixbuf.Pixbuf.new_from_file("tone.jpg")
        # cairo (2D)
        self.da = Gtk.DrawingArea()
        self.da.connect("draw", self.on_draw)
        # OpenGL ES (3D)
        embed = GtkClutter.Embed()
        stage = embed.get_stage()
        stage.set_background_color(Clutter.Color.new(0, 0, 0, 255))
        stage.connect("allocation-changed", self.on_stage_allocation_changed)
        self.actor = Clutter.Actor()
        image = Clutter.Image()
        image.set_data(
            self.pixbuf.get_pixels(),
            Cogl.PixelFormat.RGBA_8888 if self.pixbuf.get_has_alpha() else Cogl.PixelFormat.RGB_888,
            self.pixbuf.get_width(),
            self.pixbuf.get_height(),
            self.pixbuf.get_rowstride()
        )
        self.actor.set_content(image)
        stage.add_child(self.actor)
        # set
        vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        vbox.pack_start(self.da, True, True, 0)
        vbox.pack_start(embed, True, True, 0)
        self.add(vbox)
        self.connect("delete-event", Gtk.main_quit)
        self.show_all()

    def on_stage_allocation_changed(self, actor, box, flags):
        aw = box.x2
        ah = box.y2
        w = self.pixbuf.get_width()
        h = self.pixbuf.get_height()
        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
        self.actor.set_size(width , height)
        self.actor.set_position(x, y)

    def on_draw(self, widget, cr):
        d_width = widget.get_allocated_width()
        d_height = widget.get_allocated_height()
        cr.set_source_rgb(0, 0, 0)
        cr.rectangle(0, 0, d_width, d_height)
        cr.fill()
        p_width = self.pixbuf.get_width()
        p_height = self.pixbuf.get_height()
        if (d_width * p_height) > (d_height * p_width):
            width = p_width * d_height / p_height
            height = d_height
        else:
            width = d_width
            height = p_height * d_width / p_width
        pixbuf = GdkPixbuf.Pixbuf.scale_simple(self.pixbuf, width, height, GdkPixbuf.InterpType.BILINEAR)
        Gdk.cairo_set_source_pixbuf(cr, pixbuf, (d_width-width)/2, (d_height-height)/2)
        cr.paint()

GtkClutter.init()
DrawWindow()
Gtk.main()

動かす。

上半分が cairo で下が Clutter です。
拡大はまったく同じ描写になるけど縮小は全然違うものになることが解る。
なんだよこの 3D 描写。。。。。

画像処理や 3D に詳しいわけではないので細かい解説はできないけど。
単なるラスターデータとして扱う 2D と RGB として扱う 3D の違いだろう。
網点を RGB 色として認識した結果こんな悲惨なことに。

とにかく 3D では小さめな画像かアニメ調のベタ塗りでないとこんなことになる。
スクリーントーンを多用するコミック表示には徹底的に向いていないようです。

Clutter で cairo が使える手段があるのはそういうことか。
cairo で作り替えしよ。

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);

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