Treeコンポーネントはじめました

Swingをラップすればいいという話もありますが、JavaFXの綺麗なアニメーションやエフェクトを有効に使うためにもJavaFXで実装を開始しました。

動作サンプルはこちら。まだ粗々ですが、骨格はできていると思います。

TreeNodeはmixin

今回、mixinでTree構造を表しました。Javaであればインターフェイスでやる所ですが、JavaFXではmixinが使えるため、ほとんどの実装もTreeNodeクラスに含めてしまう事ができます。

public mixin class TreeNode {
    /** 子ノード */
    public-init protected var children: TreeNode[];
    /** 親ノード */
    public-read var parent: TreeNode;
    /** Leafノードである場合true */
    public-read var leaf: Boolean = bind children.size() == 0;
    /**
     * 子ノードを追加する.
     */
    public function append(child: TreeNode):Void {
        insert child into children;
        child.parent = this;
    }
    public function toLabel(): String {
        toString();
    }
    postinit {
        // 初期値として指定した場合の親ノード設定
        for (child in children) child.parent = this;
    }
}

特に難しい所はありませんが、childrenをpublic-initにしてあるのはJavaFXっぽい所です。public-initにする事で手続き的にappend, append...とやらずとも宣言的にツリーを構築できます。その代わり、初期化時に指定したchildrenについてはpostinitで親ノードを設定してあげます。
使用例はこんな感じ。

class Item extends TreeNode {
    var name: String;
    override function toLabel() { name; }
}
var item = Item {
    name: "Root"
    children: [
        Item {
            name: "Category-1"
            children: [
                Item { name: "Menu-1-1" }
                Item { name: "Menu-1-2" }
            ]
        }
        Item {
            name: "Category-2"
            children: [
                Item { name: "Menu-2-1" }
                Item { name: "Menu-2-2" }
            ]
        }
    ]
}

ItemクラスはTreeNode をmixinするので実装がほとんどありません。ラベルとして使用する文字列を返す関数のみをオーバーライドしています。
また、Itemのインスタンスは3段階までネストしたツリー構造ですが、宣言的に記述できています。Javaで手続き的に構築する場合に比べて非常に簡潔で、構造に即した形となっているのがポイントです。

Tree自体の実装はまだあまり進んでいませんが、これまで作ってきたGridとAccordionViewを組み合わせて構築しています(ソース)。

CustomNodeとContainerの継承

実装自体は結構すぐに固まったのですが、イベントの伝搬やレイアウトで結構はまりました。
その中で気付いた点ですが、まずVBoxなどのContainer系クラスを継承するのは避けたほうがいいと言うことです(Containerは除く)。どうもレイアウトを行っているタイミングが、VBoxなどのinitであるため、継承したクラスのinitなどで処理を行っても初期表示のタイミングで計算が狂っていることがあるようです。doLayoutなどを強制的に呼べば解決するかとも思いましたが、そうでもありませんでした。また、initの中身に関しては今後変わる可能性もあるわけで、後からinitが行われる継承によるVBoxなどのクラスは避けるべきという結論です。
代わりになるのがCustomNodeで生成するノードをVBox等にすることです。こうすることで、シーングラフは1階層深くなりますが、レイアウトがおかしいという問題は「だいたい」回避できます。