Kotlin
processing
macos

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

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

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で開発している人は次のような感じ。

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()の実行が遅れたりするかもしれない。

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のframenativeFrameに置き換わっただけで、アプローチは変わらない。surfaceのリサイズを有効にすること。

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)

フルスクリーンをウィンドウに変更する

前項と反対の処理をするだけ。

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)
java.awt.Frame#addNotify (Oracle Java Platform SE8)
Javaでマルチスクリーンな場所の指定 (Hatena: hidesuke)