Sui ― GUI Library for LÖVE (1) フォーカス

上の2つに次いで、3つ目のSuiの記事。

今日はフォーカス機能を実装した。これがあれば、フォーカスのあるウィジェットだけ反応するようにもできる。

前回書いた通り、Suiのウィジェットは単なる関数の詰め合わせなので、新しい機能を追加したい時は、新しい関数を追加してやるだけで済む。フォーカス機能を実現するために、changefocusとfocusという2つの関数が追加され、これらの関数を操作するビルダー関数も用意されている。
大事なビルダー関数は、sui.focusstop, sui.focusroot, sui.focusの3つだ。

ui = sui.focusroot sui.vbox 5, {
    sui.focusstop sui.focusbc {64, 64, 64, 255}, sui.label 200, 24, "Hello, world!"
    sui.focusstop sui.focusbc {64, 64, 64, 255}, sui.pie 100, 0.8
    sui.focusstop sui.bc {32, 32, 32, 255}, sui.focusbc {64, 64, 64, 255},
        sui.hbar 200, 16, 0.8
}

sui.focusstopはウィジェットにchangefocus関数を追加するビルダー関数。changefocus関数を持っているウィジェットは、フォーカスを持てるようになる。フォーカスが移動したときにはchangefocus関数が連鎖的に呼ばれ、フォーカスを得た時と失った時に、子ウィジェットのfocusイベントハンドラーが呼び出されるという構成になっている。

sui.focusrootはchangefocus関数を「閉じる」ためのビルダー関数。changefocusはフォーカスをリレーして伝えるが、フォーカスが一巡したらまた最初のウィジェットにフォーカスを戻す処理を追加する。

sui.focusbcはフォーカスを持っているときに背景色をつけるビルダー関数で、sui.focusで実装されている。sui.focusはfocusイベントハンドラーを追加する。

sui.focusbc = (color, widget) ->
	focused = false
	sui.focus (f) -> focused = f,
		sui.bc (-> if focused then bang(color)), widget

sui.vboxなどが生成するコンテナーウィジェットは、子ウィジェットのchangefocusを順番に呼び出していくので、コンテナーが入れ子になっている場合や、focusstopが入れ子になっている場合も適切にフォーカスがリレーするようになっている。

フォーカスの移動は、ウィジェットのchangefocus関数を呼び出す。実引数として渡しているのはフォーカス移動の方向で、trueを渡すと逆順に移動する。この例では、シフトキーを押している場合に逆向きの移動になる。

love.keypressed = (key, unicode) ->
    switch key
        when 'tab'
            ui.changefocus(love.keyboard.isDown('lshift', 'rshift'))
    return

この例では、フォーカスはキーボードで操作した場合にだけ移動する。マウスクリックでフォーカスを移動させる場合には、フォーカスを指定したウィジェットに移動させる処理がいるが、そのためにはchangefocusに移動させる先のウィジェットを渡せば良い。少しわずらわしいが、次のclick_focusstopのようなビルダー関数を用意することで、クリックでフォーカスを移す処理を実現できる。

local ui
click_focusstop = (widget) ->
	local obj
	obj = sui.focusstop sui.mousepressed (-> ui.changefocus(obj)),
		widget
	return obj
ui = sui.focusroot sui.vbox 5, {
    click_focusstop sui.focusbc {64, 64, 64, 255}, sui.label 200, 24, "Hello, world!"
    click_focusstop sui.focusbc {64, 64, 64, 255}, sui.pie 100, 0.8
    click_focusstop sui.bc {32, 32, 32, 255}, sui.focusbc {64, 64, 64, 255},
        sui.hbar 200, 16, 0.8
}

フォーカスを持っているときだけ反応するイベントハンドラーを追加するsui.focuseventを使えば、テキストボックスやキーボードで操作できるボタンを実装できる。

toggleButton = ->
  press = ->
    state = not state
  sui.focusevent 'keypressed', ((key, unicode) -> if key == 'return' or key == ' ' then press()),
    sui.clicked press, sui.bc {50, 50, 50}, sui.margin 10, 10, sui.focusfc {255, 255, 128, 255},
      sui.label 60, 16, (-> if state then "[ ] Stop" else "|> Play")

textbox = ->
  text = ''
  sui.vbox 5, {
    sui.focusoption {
      [true]: sui.label 200, 16, -> "Type away! #text = " .. tostring(#text)
      [false]: sui.label 200, 16, -> "Focus on me!"
    }
    sui.focusevent 'keypressed', (key, unicode) ->
        if key == 'backspace'
          text = string.sub(text, 1, -2)
        elseif 0x20 <= unicode and unicode < 0x7F
          text ..= string.char(unicode),
      sui.label 200, 16, -> text
  }

デモ動画