Processing3のmacOS版でウィンドウやフルスクリーンを操作する

Processing3になってからウィンドウ（フレーム）操作が変更され、`frame.setLocation()`のようなメソッドが現在のバージョン（3.3.6）のmacOS版では基本的に動作しなくなった。（WindowsやLinuxは未検証なので、誰か教えて）
さて、どうしてもウィンドウを操作したい場合は次のようにSurfaceから本物のフレームを取ってくる必要がある。

```java:Processing
import processing.awt.PSurfaceAWT;
import java.awt.Frame;
import java.awt.Point;

void mousePressed() {
  PSurfaceAWT.SmoothCanvas canvas =
 (PSurfaceAWT.SmoothCanvas)surface.getNative();
  Frame nativeFrame = canvas.getFrame();

  Point pos = nativeFrame.getLocation();
  nativeFrame.setLocation(pos.getX()+100, pos.getY())
}
```

Kotlinで開発している人は次のような感じ。

```kotlin:ProcessingKotlin.kt
import processing.awt.PSurfaceAWT
import processing.core.*
import java.awt.Frame

class MainApplication : PApplet() {
  // プロパティにしておくとProcessing2の頃みたいに操作できる
  val nativeFrame: Frame
    get() {
      val canvas = surface.native as PSurfaceAWT.SmoothCanvas
      return canvas.frame
    }

  override fun mousePressed() {
    val pos = nativeFrame.location
    nativeFrame.setLocation(pos.x + 100, pos.y)
  }

  fun run(args: Array<String>) : Unit
    = PApplet.main(this.javaClass.name)
}
```

# 応用
以下は発展的？な付録として添えておく。ただし全てKotlinで記述しているので、純粋なProcessing使いの方は読み替えてください。

## Notifyの役割
詳しくは分からないが、AWTの仮想的に作られたフレームをOSが提供するウィンドウやイベントに割り当てる機能のようだ。`setup()`内で`setLocation()`などが上手く動かない場合はNotifyを一度解除してから実行すると良い。
ただし、一度ウィンドウが消えるので、起動時に多少チラついたり`draw()`の実行が遅れたりするかもしれない。

```kotlin:InitializeLocation.kt
override fun settings() {
  size(800, 600) // ウィンドウサイズ
}

override fun setup() {
  nativeFrame.removeNotify()
  nativeFrame.setSize(width, height) // Surfaceとズレる場合の対策
  nativeFrame.setLocation(100, 100) // ウィンドウの初期座標
  nativeFrame.addNotify()
}
```

不必要にremoveしたりaddしたりしていると、Surfaceのバッファ関連でAWTに怒られる。具体的な条件はよく分からなかった。

## ウィンドウをフルスクリーンに変更する
基本的にProcessing2の`frame`が`nativeFrame`に置き換わっただけで、アプローチは変わらない。surfaceのリサイズを有効にすること。

```kotlin:ChangeToFullScreen.kt
// クリックすると接続されているディスプレイ順にフルスクリーン化される
override fun mousePressed() {
  changeToFullScreen(i)
  i++
}

// 使い方はProcessing標準の fullScreen() と同じ
fun changeToFullScreen(display: Int) {
  // Get display information
  val gEnv = GraphicsEnvironment.getLocalGraphicsEnvironment()
  val gDevice = if (display == 0 || display > gEnv.screenDevices.size)
    gEnv.defaultScreenDevice
  else
    gEnv.screenDevices[display - 1]
  val bound = gDevice.defaultConfiguration.bounds

  // Change fullscreen
  val reqFlush = !nativeFrame.isUndecorated
  if (reqFlush) {
    nativeFrame.removeNotify()
    nativeFrame.isAlwaysOnTop = true
    nativeFrame.isUndecorated = true
  }

  nativeFrame.setLocation(bound.x, bound.y)
  nativeFrame.setSize(bound.width, bound.height)
  surface.setSize(bound.width, bound.height)

  if (reqFlush) {
    nativeFrame.addNotify()
  }
}
fun changeToFullScreen() : Unit
    = changeToFullScreen(0)
```

## フルスクリーンをウィンドウに変更する
前項と反対の処理をするだけ。

```kotlin:ChangeToWindow.kt
fun changeToWindow(width: Int, height: Int, x: Int?, y: Int?) {
  val reqReset = nativeFrame.isUndecorated
  if (reqReset) {
    nativeFrame.removeNotify()
    nativeFrame.isUndecorated = false
    nativeFrame.isAlwaysOnTop = false
  }

  if (x != null && y != null) {
    nativeFrame.setLocation(x, y)
  }

  nativeFrame.setSize(width, height)
  surface.setSize(width, height)

  if (reqReset) {
    nativeFrame.addNotify()
  }
}
fun changeToWindow(width: Int, height: Int) : Unit
    = changeToWindow(width, height, null, null)
```

## その他…
思いつき次第、追記する…かも。

# 参考文献
・[Processing 3.0で実行中にwindowのsizeの変更やタイトルバーを非表示にする方法 (Qiita: kenji_5884)](https://qiita.com/kenji_5884/items/f16e8dd549feae8f6d2f)
・[java.awt.Frame#addNotify (Oracle Java Platform SE8)](https://docs.oracle.com/javase/jp/8/docs/api/java/awt/Frame.html#addNotify--)
・[Javaでマルチスクリーンな場所の指定 (Hatena: hidesuke)](http://d.hatena.ne.jp/hidesuke/20060920/p2)