packages feed

keid-render-basic-0.1.9.0: src/Engine/UI/Message.hs

module Engine.UI.Message
  ( Process
  , Input(..)
  , spawn
  , spawnFromR
  , mkAttrs

  , Observer
  , Buffer
  , newObserver
  , observe
  ) where

import RIO

import Control.Monad.Trans.Resource (ResourceT)
import Control.Monad.Trans.Resource qualified as Resource
import Data.Text qualified as Text
import Data.Type.Equality (type (~))
import Data.Vector.Storable qualified as Storable
import Geomancy (Vec4)
import Geomancy.Layout.Alignment (Alignment)
import Geomancy.Layout.Box (Box(..))
import Geomancy.Vec4 qualified as Vec4
import UnliftIO.Resource (MonadResource)
import Vulkan.Core10 qualified as Vk

import Engine.Types qualified as Engine
import Engine.Vulkan.Types (MonadVulkan)
import Engine.Worker qualified as Worker
import Render.Font.EvanwSdf.Model qualified as EvanwSdf
import Render.Samplers qualified as Samplers
import Resource.Buffer qualified as Buffer
import Resource.Font.EvanW qualified as Font

type Process = Worker.Merge (Storable.Vector EvanwSdf.InstanceAttrs)

spawn
  :: ( MonadResource m
     , MonadUnliftIO m
     , Worker.HasOutput box
     , Worker.GetOutput box ~ Box
     , Worker.HasOutput input
     , Worker.GetOutput input ~ Input
     )
  => box
  -> input
  -> m Process
spawn = Worker.spawnMerge2 mkAttrs

spawnFromR
  :: ( MonadResource m
     , MonadUnliftIO m
     , Worker.HasOutput box
     , Worker.GetOutput box ~ Box
     , Worker.HasOutput source
     )
  => box
  -> source
  -> (Worker.GetOutput source -> Input)
  -> m Process
spawnFromR parent inputProc mkMessage = do
  inputP <- Worker.spawnMerge1 mkMessage inputProc
  spawn parent inputP

data Input = Input
  { inputText         :: Text
  , inputFontId       :: Int32
  , inputFont         :: Font.Container
  , inputOrigin       :: Alignment
  , inputSize         :: Float
  , inputColor        :: Vec4
  , inputOutline      :: Vec4
  , inputOutlineWidth :: Float
  , inputSmoothing    :: Float
  }

mkAttrs :: Box -> Input -> Storable.Vector EvanwSdf.InstanceAttrs
mkAttrs Box{..} Input{..} =
  if inputSize < 1 then mempty else Storable.fromList do
    Font.PutChar{..} <- chars
    pure EvanwSdf.InstanceAttrs
      { vertRect     = Vec4.fromVec22 pcPos pcSize
      , fragRect     = Vec4.fromVec22 pcOffset pcScale
      , color        = inputColor
      , outlineColor = inputOutline -- vec4 0 0.25 0 0.25
      , textureId    = inputFontId
      , samplerId    = samplerId
      , smoothing    = inputSmoothing -- 1/16
      , outlineWidth = inputOutlineWidth -- 3/16
      }
  where
    (scale, chars) = Font.putLine
      size
      position
      inputOrigin
      inputSize
      inputFont
      (Text.unpack inputText)

    samplerId =
      if scale > 1 then
        Samplers.linear Samplers.indices
      else
        Samplers.linear Samplers.indices

type Buffer = Buffer.Allocated 'Buffer.Coherent EvanwSdf.InstanceAttrs

type Observer = Worker.ObserverIO Buffer

newObserver :: Int -> ResourceT (Engine.StageRIO st) Observer
newObserver initialSize = do
  messageData <- Buffer.createCoherent (Just "Message") Vk.BUFFER_USAGE_VERTEX_BUFFER_BIT initialSize mempty
  observer <- Worker.newObserverIO messageData

  context <- ask
  void $! Resource.register do
    vData <- readIORef observer
    traverse_ (Buffer.destroy context) vData

  pure observer

observe
  :: ( MonadVulkan env m
     , Worker.HasOutput source
     , Worker.GetOutput source ~ Storable.Vector EvanwSdf.InstanceAttrs
     )
  => source
  -> Observer
  -> m ()
observe messageP observer =
  Worker.observeIO_ messageP observer Buffer.updateCoherentResize_