packages feed

keid-frp-banana-0.1.1.0: src/Engine/ReactiveBanana/Window.hs

module Engine.ReactiveBanana.Window where

import RIO

import Control.Monad.Trans.Resource (ResourceT)
import Engine.ReactiveBanana (eventHandler)
import Engine.Types (StageRIO)
import Engine.Types qualified as Engine
import Engine.UI.Layout qualified as Layout
import Engine.Window.CursorPos qualified as CursorPos
import Engine.Window.Drop qualified as Drop
import Engine.Window.Key qualified as Key
import Engine.Window.MouseButton qualified as MouseButton
import Engine.Window.Scroll qualified as Scroll
import Engine.Worker qualified as Worker
import Geomancy (Vec2, vec2, (^/))
import GHC.Float (double2Float)
import Reactive.Banana ((<@>), (<@>))
import Reactive.Banana qualified as RB
import Reactive.Banana.Frameworks qualified as RBF
import Vulkan.Core10 qualified as Vk

-- * Wrapped Engine.Window.* callbacks

-- | Set up a window callback to fire window "CursorPos"  events.
allocateCursorPos :: ResourceT (StageRIO st) (RBF.MomentIO (RB.Event (Double, Double)))
allocateCursorPos = eventHandler $ CursorPos.callback . curry

-- | Set up a window callback to fire window "Drop"  events.
allocateDrop :: ResourceT (StageRIO st) (RBF.MomentIO (RB.Event [FilePath]))
allocateDrop = eventHandler Drop.callback

{- | Set up a window callback to fire window "MouseButton"  events.

To prevent clicks when hovering over some ImGui window wrap in a `RB.whenE` filter:

@
imguiCaptureMouse <- RBF.fromPoll ImGui.wantCaptureMouse
mouseButtonE <- fmap (RB.whenE $ fmap not imguiCaptureMouse) fromMouseButton
@
-}
allocateMouseButton
  :: ResourceT
      (StageRIO st)
      ( RBF.MomentIO
          ( RB.Event
            ( MouseButton.ModifierKeys
            , MouseButton.MouseButtonState
            , MouseButton.MouseButton
            )
          )
        )
allocateMouseButton = eventHandler MouseButton.callback

-- | Set up a window callback to fire window "Scroll"  events.
allocateScroll :: ResourceT (StageRIO st) (RBF.MomentIO (RB.Event (Double, Double)))
allocateScroll = eventHandler $ Scroll.callback . curry

{- | Set up a window callback to fire window "Key"  events.

To prevent clicks when ImGui is busy with text input wrap in a `RB.whenE` filter:

@
imguiCaptureKeyboard <- RBF.fromPoll ImGui.wantCaptureKeyboard
keyE <- fmap (RB.whenE $ fmap not imguiCaptureKeyboard) fromKey
@
-}
allocateKey :: ResourceT (StageRIO st) (RBF.MomentIO (RB.Event (Int, (MouseButton.ModifierKeys, Key.KeyState, Key.Key))))
allocateKey = eventHandler $ Key.callback . curry

-- * 'Engine.UI.Layout' helpers

-- | Screen-sized layout base.
setupScreenBox
  :: (forall a. StageRIO env a -> RBF.MomentIO a)
  -> RBF.MomentIO (RB.Behavior Layout.Box)
setupScreenBox unlift = do
  screenExtent <- unlift Engine.askScreenVar >>=
    RBF.fromPoll . Worker.getOutputData

  let
    screenSize =
      screenExtent <&>
        \Vk.Extent2D{width, height} ->
          vec2
            (fromIntegral width)
            (fromIntegral height)

    screenBox =
      screenSize <&> \size ->
        Layout.Box
          { boxPosition = 0 -- XXX: since Camera.spawnOrthoPixelsCentered
          , boxSize     = size
          }

  pure screenBox

-- | Project window cursor position to layout.
setupCursorPos
  :: RB.MonadMoment m
  => m (RB.Event (Double, Double))
  -> RB.Behavior Layout.Box
  -> m (RB.Event Vec2, RB.Behavior Vec2)
setupCursorPos fromCursorPos screenBox = do
  cursorPosRawE <- fromCursorPos
  let cursorPosE = convertPos <$> screenBox <@> cursorPosRawE

  cursorPos <- RB.stepper
    (1/0) -- XXX: prevent accidental flash of hover at (0, 0)
    cursorPosE
  pure (cursorPosE, cursorPos)
  where
    convertPos Layout.Box{boxSize} (cx, cy) =
      -- XXX: since Camera.spawnOrthoPixelsCentered
      vec2 (double2Float cx) (double2Float cy) -
      boxSize ^/ 2

-- | Set up a per-button collection of fused (position, modifier) click ("button pressed") events.
setupMouseClicks
  :: RBF.MomentIO (RB.Event (MouseButton.ModifierKeys, MouseButton.MouseButtonState, MouseButton.MouseButton))
  -> RB.Behavior cursor
  -> RBF.MomentIO (MouseButton.Collection (RB.Event (MouseButton.ModifierKeys, cursor)))
setupMouseClicks fromMouseButton cursorPos = do

  mouseButtonE <- fromMouseButton

  -- XXX: Set up cursor event fusion, driven by mouseButtonE
  mouseButtons' <- sequenceA @MouseButton.Collection $ pure RBF.newEvent

  let
    dispatchButtons pos (mods, state, mb) =
      MouseButton.whenPressed state $
        -- XXX: Use one event handler to drive multiple derived events
        snd (MouseButton.atGlfw mouseButtons' mb) (mods, pos)

  RBF.reactimate $
    dispatchButtons <$> cursorPos <@> mouseButtonE

  pure $ fmap fst mouseButtons'