JUCEにおけるz-orderの扱い

また音楽技術Advent Calendar 2019JUCE Advent Calendar 2019のcross postingです。(現状どっちもとても埋まる気がしない)

JUCE GUIのComponentには、その内容としてchild componentsをaddAndMakeVisible()関数を使って載せることができます。これはやや設計として失敗の雰囲気があるものの(全てのComponentが子をもつ設計は200x年代前半の臭いがありますね)、古典的には一般的なGUIコンポーネントの設計です。そして、コンポーネントの描画はpaint()関数で行われます。JUCEの子要素は自分でレイアウトして、自身の描画もpaint()で行うのが基本です。あれ? ちょっと雲行きが怪しくなってきましたね…? 大丈夫、子要素のpaint()関数も呼び出されるので問題ありません。

子要素はデフォルトではabsolute layoutのようにpaint()で渡されたGraphicsで渡される領域の範囲内で描画します。描画する領域は親から渡され、子の描画はその子のpaint()関数が行う…ということは、複数の子がある場合、描画順序によっては、ある子の描画内容を後から別の子が上書きしてしまう可能性があります。

これでは困りますね。少なくとも描画順序を制御できなければ困ります。こういう時にわれわれが使うのは、そう、「前面に移動」「背面に移動」です。MSOfficeですら制御できるアレです。もう少し真面目に言うとz-orderですね。

このz-orderですが、JUCEのComponentには対応するプロパティがありません。z-order無いのかよ!となりそうですが、z-orderを指定できる場所がひとつあります。Componentに子要素を追加する時に使うaddAndMakeVisible()関数です。

void Component::addAndMakeVisible (Component * child, int zOrder = -1)

zOrder : The index in the child-list at which this component should be inserted. A value of -1 will insert it in front of the others, 0 is the back.

これで順番を指定できそうですね。さっそく具体例を見てみましょう。簡単な重ね合わせの生じるデモを作ったので、これを土台に検証します。

f:id:atsushieno:20191210024147p:plain

#pragma once  
#include "../JuceLibraryCode/JuceHeader.h"  
  
class Component1 : public Component {  
public:  
  void paint(Graphics& g) override {  
    g.fillAll(Colours::green);  
  }  
};  
  
class Component2 : public Component {  
public:  
  void paint(Graphics& g) override {  
    g.fillAll(Colours::blue);  
  }  
};  
  
class MainComponent : public Component  
{  
  Component1 c1;  
  Component2 c2;  
  Label l1{"label1", "Label1"};  
  Label l2{"label2", "Label2"};  
public:  
  //==============================================================================  
  MainComponent() {  
  c1.setBounds(0, 0, 200, 200);  
  c2.setBounds(100, 100, 200, 200);  
  l1.setBounds(0, 0, 200, 200);  
  l2.setBounds(100, 100, 200, 200);  
  addAndMakeVisible(c1);  
  addAndMakeVisible(c2);  
  addAndMakeVisible(l1);  
  addAndMakeVisible(l2);  
  
  setSize (300, 300);  
  }  
  
  JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)  
};

JUCEのソースファイルなのでPIPにしてもよかったのですが、とりあえずデフォルトテンプレートのMainComponent.hを書き換えてMainComponent.cppの中身を空っぽにしただけです。多分ビルドできるでしょう。

緑の矩形と青の矩形が重なり合っていますね。この画面で、緑を青の上に出したいと思ったら、zOrderを指定してaddAndMakeVisible()を呼び出すと良いということですね。

  addAndMakeVisible(c1, 1);  
  addAndMakeVisible(c2, 0);  
  addAndMakeVisible(l1);  
  addAndMakeVisible(l2);  

f:id:atsushieno:20191210024254p:plain

なるほど順番が変わりましたね! 実際にレイアウトを調整するときは間に他の要素が入るかもしれないので、zOrderの差を大きめに設定しておきましょう。

addAndMakeVisible(c1, 100);  
addAndMakeVisible(c2, 1);  
addAndMakeVisible(l1, 110);  
addAndMakeVisible(l2, 10);

f:id:atsushieno:20191210024147p:plain

( ゚д゚) ・・・
 
(つд⊂)ゴシゴシ
 
(;゚д゚) ・・・

あ…れ…? なんか想定外の挙動ですね…?

zOrderってなんか値域とかあるんだっけ…?と思いながら情報を探してみると、こんなforumの議論が…

forum.juce.com

In particular, if a component has N children, then all z-indices >= N will be considered equivalent, because of this code in Component::addChildComponent:

「子要素の数がNだったらzOrderの値がN以上だったら全部Nに揃えられる」…そマ?

まじすか…どうもzOrderの設計はやっつけっぽい…。その後のスレッドでは「Componentのメモリ使用量は可能な限り最小限にしてあって云々…」とあるのですが、ホントか〜? ホントにメモリ使用量を気にするんならComponentがMouseListenerを実装しているのおかしくね〜?とか思ったりしちゃったり何だったりしますね…

ともあれ、zOrderはこういう感じで「期待通りに動作させるには慎重に使わないといけない」ものなので、使うときは十分に注意しましょう。たぶんaddChildComponent()がひと通り済んだ時点で適切な順序でソートしたほうが良いです。

以上juce_gui_basicsの小ネタでした。