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 に早速採用、永遠に実験用アプリ…

JXA NSWindow

今回は JXA でウインドウを作ってみる。

実はウチの PyGObject Tips ページのアクセス数で解るんだけどさ。
ウインドウを作るのページだけブッチギリで多いのよ、初心者ってそんなもんだ。
なんだかなぁ、プログラミングが面白いのはその先からだと思うんですけど。
ということで画像も表示してみる。

検索すると色々な実装があるけど結果は全部同じだね。
それなら簡潔で理解が早い人が多いっぽい実装を選んでみよう。

でも気に入らないのは大半の人がウインドウを閉じると終了するコードだ。
おい macOS だぞ、メニューバーに command+Q は必須だろ?
それと無意味な即時実行多すぎ、多分よく解っていないんだろうけど。

NSApp.servicesMenu in JXA ? GitHub

なんだよ、command+Q メニューはこんなにアッサリ実装できるんかい。
まさかこんなところでバックコーテーションを使うとは。
alloc.init だけなら new メソッドでいいみたい、ふむふむ。

しかし一回しか使わない関数なら無名関数の即時実行でいいのに。
即時実行ってそういう場合に使うと思うんだが。
ということで、こんなサンプルコードになりました。

#!/usr/bin/osascript

ObjC.import("Cocoa");

const imgePath = $("/Users/sasakima-nao/Pictures/[シクシクおよよ]三嶋ゆらら(SSR).jpg");

/**
 * Contents
 */
let image = $.NSImage.alloc.initWithContentsOfFile(imgePath);
if (image.isNil()) {
    console.log("Image Not Found.");
}
let imageView = $.NSImageView.alloc.initWithFrame(
    $.NSMakeRect(0, 0, 320, 400)
);
imageView.setImageScaling($.NSScaleToFit);
imageView.setImage(image);

/**
 * Window
 */
let window = $.NSWindow.alloc.initWithContentRectStyleMaskBackingDefer(
    $.NSMakeRect(0, 0, 320, 400),
    $.NSTitledWindowMask
    | $.NSClosableWindowMask
    | $.NSMiniaturizableWindowMask,
    //| $.NSResizableWindowMask,
    $.NSBackingStoreBuffered,
    false
);
window.title = $("[シクシクおよよ]三嶋ゆらら(SSR)");
window.center;
window.orderFrontRegardless;
window.contentView.addSubview(imageView);

/**
 * Application
 */
let app = $.NSApplication.sharedApplication;
app.setActivationPolicy($.NSApplicationActivationPolicyRegular);
app.mainMenu = function() {
    
    function newMenu(title, action, key) {
       return $.NSMenuItem.alloc.initWithTitleActionKeyEquivalent(title, action, key);
    }
    
    const appName = "Test Window";
    
    const mainMenu = $.NSMenu.new;
    const itemApp  = $.NSMenuItem.new;
    const menuApp  = $.NSMenu.new;
    itemApp.submenu  = menuApp;
    mainMenu.addItem(itemApp);
    menuApp.addItem(newMenu(`Quit ${appName}`, 'terminate:', 'q') );

    return mainMenu;
}();
app.run;

やばい、ゆららちゃんがこんなにカワイイとは!
つい炭酸全部使ってフルマカロン取っ、、、、、いやそれはどうでもよくて。

macOS アプリって Application と Window に何も関連性が無いんだね。
あぁだから macOS では Window を全部閉じてもアプリは終了しないのか。

GTK+, WPF, VCL 等は全部 Application class が Window を管理している。
で、管理する Window が無くなったら Application が終了する、という流れ。
mac は本当に独特なんだなと。

しかしメソッドに括弧が無いのは相変わらず慣れない。
app.run にすら不要って意味ワカンネ!

run JXA in Visual Studio Code

Visual Studio Code (以下 Code)で新たな発見。

他のエディタと同じファイルを開いて他側にて上書き保存。
すると即座に Code は全自動で読み直ししてくれる、凄い!
更に開いているファイルを削除すると全自動でタブを閉じてくれる、凄い!

正直迷惑な場合が多いのですけど…

筆者は macOS で日本語が入力できない Gedit の代用で使っている。
alt(option)+上下矢印キーで行入れ替え
fn+左矢印キー(Home) でインデントの先頭へ移動
shift+tab で選択していなくてもインデント戻し
が可能なエディタをこの2つしか知らないのでしかたがない。

ウザいだけのインテリセンスを無効にして騙し騙し使っていたり。
問題は F5 デバックだ。
そもそも F5 にしている理由は Visual Studio に合わせたのですし。
Gedit で Python スクリプトを debug – L’Isola di Niente

Code で F5 を叩くと、なんで node.js 専用なんだよ!
筆者は JXA しかやるつもりは無い、代用手段は無いのか?

特集:Visual Studio Code早分かりガイド:Visual Studio Codeの使い勝手をよくするツール (1/5) – @IT

あるみたい。
ということで F5 に割り付けしてみよう!

run task をコマンドパレットに打ち込んでも出ない、あれ?
って「タスク」と日本語で打ち込まないといけなくなったようで。
shift+command+B は普通に使えるようだ、って覚えにくいよ。

tasks.json はディレクトリ毎に作られるようで。
とにかく以下のように書き換える。

{
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "0.1.0",
    "command": "osascript",
    "isShellCommand": true,
    "args": ["${file}"],
    "showOutput": "always"
}

${file} 変数はココで使えるかどうか見つからないけど実験をかねて。
んで、キーボードショートカットの f5 を上書き。

// 既定値を上書きするには、このファイル内にキー バインドを挿入します
[
    {
        "key": "f5",
        "command": "workbench.action.tasks.build"
    }
]

ここまでやって f5 を叩いてみる。

おぉコレは!
と思ったけどこの機能はファイル単体で開いた時では使えないのね。
使うには毎回 [フォルダを開く] をやらなきゃいけないみたい。
うーん…

bash macOS

bash は大文字と小文字を厳格に区別する。
しかし macOS の bash は echo, pwd 等の大小文字を区別しない。
ところが if や変数名は普通に区別することに今頃気がつく。

shopt あたりにそうなるオプションが…無かった。
$- の値も Fedora と同じ himBH だった。
じゃあ何故大文字でも echo 等が使えるのだ?

解ってしまえば単純な話で。
組み込みコマンドではなく /bin/echo が使われていただけ。
ファイル名の大文字小文字を区別しないからこんな変なことになるんじゃ!

bash indirection

${!prefix*}
という記述で bash はその接頭子の変数名をブレース展開してくれるらしい。
ビックリマークは間接参照という意味らしい。

さっそくテストコードを書いてみたら見事にハマった!

#!/bin/sh

GF_top='心美 文緒 くおえ'
GF_loli='苗 睦 桃子'

echo ${!GF_*}

for gf in ${!GF_*}; do
    #for s in $gf; do
    for s in ${!gf}; do
        echo $s
    done
done

$gf という記述ではこの場合変数展開できない。
変数名で出力されるので ${${gf}} とかワケワカなことしてみたり。

よーく考えたら当然だった。
$gf なんて変数は定義していないんですもの。
どんな言語でもこんなことはできないよ。

なのでココでも間接参照で実際の変数を見つけてもらう必要がある。
シェルスクリプトって上記ができるとつい勘違いするよね、筆者だけ?