packages feed

nyx-game-1.0.1.0: app/TextBox.hs

{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses  #-}
{-# LANGUAGE DuplicateRecordFields  #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeSynonymInstances  #-}
{-# LANGUAGE FlexibleInstances  #-}
{-# LANGUAGE OverloadedStrings  #-}

module TextBox where

import SDL.Vect (V4(..))
import qualified SDL
import qualified SDL.Font as SDLF
import qualified Play.Engine.MySDL.MySDL as MySDL

import Data.Monoid ((<>))
import Data.Maybe
import Control.Arrow
import Control.Lens
import Control.Monad.Except
import Play.Engine hiding (renderText)
import qualified Control.Monad.State as SM
import qualified Data.Text as T
import qualified Play.Engine.ListZipper as Zip


data Loc
  = Top
  | Bottom
  | All
  deriving (Show, Eq, Ord)

data TextBox
  = TextBox
  { _avatar :: Maybe SDL.Texture
  , _font :: SDLF.Font
  , _text :: Zip.ListZipper (Int, T.Text)
  , _posY :: !Loc
  , _pos :: {-# UNPACK #-} !IPoint
  , _size :: {-# UNPACK #-} !IPoint
  , _textSpeed :: Int
  , _textTimer :: Int
  }

makeFieldsNoPrefix ''TextBox

wantedAssets :: [(String, MySDL.ResourceType FilePath)]
wantedAssets =
  [ ("unispace", MySDL.Font "unispace/unispace.ttf")
  ]

make :: Loc -> Int -> T.Text -> Maybe SDL.Texture -> Maybe SDLF.Font -> Result TextBox
make _ _ _ _ Nothing =
  throwError ["A font was not provided to TextBox."]
make py spd txt mavatar (Just fnt) =
  pure $ TextBox
    { _avatar = mavatar
    , _font = fnt
    , _text =
      uncurry
        (Zip.ListZipper [])
      . (Prelude.head &&& tail)
      . zip (cycle [0])
      . concat
      . fmap (splitLine $ isJust mavatar)
      . T.lines
      $ txt
    , _posY = py
    , _pos = Point 0 0
    , _size = Point 0 0
    , _textSpeed = spd
    , _textTimer = spd
    }

splitLine :: Bool -> T.Text -> [T.Text]
splitLine hasAvatar txt =
  groupWhile (\t1 t2 -> plus1' (T.length t1) + T.length t2 <= (if hasAvatar then 43 else 50)) ""
  . T.words
  $ txt


groupWhile :: (T.Text -> T.Text -> Bool) -> T.Text -> [T.Text] -> [T.Text]
groupWhile test t1 = \case
  t2 : rest
    | test t1 t2 ->
      groupWhile test (t1 <> (if t1 == "" then "" else " ") <> t2) rest
    | otherwise  -> t1 : groupWhile test t2 rest
  [] -> [t1]

plus1' :: Int -> Int
plus1' n
  | n == 0 = 0
  | otherwise = n + 1

update :: Input -> TextBox -> Result (Maybe TextBox)
update input tb
  | keyClicked KeyA input || keyClicked KeyStart input
  , let (l, t) = Zip.last (tb ^. text) in l == T.length t
  = pure Nothing
  | otherwise = do
    wSize <- _windowSize <$> SM.get
    let
      locY = case tb ^. posY of
        Top -> 20
        All -> 20
        Bottom -> (wSize ^. y - 220)

    pure $ pure $ tb
      & set pos (Point 20 locY)
      & set size
        (Point (wSize ^. x - 40) $
          if tb ^. posY == All
            then wSize ^. y - 40
            else 200
        )
      & over text
        (\(txt :: Zip.ListZipper (Int, T.Text)) ->
           if
             | keyClicked KeyA input
             || keyClicked KeyStart input
             || tb ^. textSpeed == 0 ->
               fmap (\(_, t) -> (T.length t, t)) txt
             | tb ^. textTimer == 0
             && fst (Zip.get txt) < T.length (snd $ Zip.get txt) ->
               Zip.overCurr (first (+1)) txt
             | tb ^. textTimer == 0 ->
               Zip.nextStop txt
             | otherwise ->
               txt
        )
      & over textTimer (\t -> if t <= 0 then tb ^. textSpeed else t - 1)


render :: SDL.Renderer -> TextBox -> IO ()
render renderer tb
  | tb ^. size == Point 0 0 =
    pure ()
  | otherwise = do
  let
    rect = toRect (tb ^. pos) (tb ^. size)

  SDL.rendererDrawColor renderer SDL.$= V4 0 0 0 150
  SDL.fillRect renderer (Just rect)
  SDL.rendererDrawColor renderer SDL.$= V4 255 0 160 200
  SDL.drawRect renderer (Just rect)

  loc <- case tb ^. avatar of
    Nothing -> pure (fmap (+20) $ tb ^. pos)
    Just av -> do
      SDL.copy
        renderer
        av
        Nothing
        (Just $ toRect
          (fmap (+20) $ tb ^. pos)
          (Point 96 96)
        )
      pure (Point (20 + tb ^. pos . x + 96 + 20) (tb ^. pos . y + 20))
  void $ traverse (renderText renderer tb loc) $ Zip.addIndex (tb ^. text)

renderText :: SDL.Renderer -> TextBox -> IPoint -> (Int, (Int, T.Text)) -> IO ()
renderText renderer tb loc (idx, (txtPart, txt)) =
  if txtPart == 0
    then pure ()
    else do
      texture <- SDL.createTextureFromSurface renderer
        =<< SDLF.solid
          (tb ^. font)
          (V4 255 255 255 255)
          (T.take txtPart txt)
      ti <- SDL.queryTexture texture
      SDL.copy
        renderer
        texture
        Nothing
        (Just $ toRect
          (over y ((+) (idx * 35)) loc)
          (Point (fromIntegral $ SDL.textureWidth ti) (fromIntegral $ SDL.textureHeight ti))
        )
      SDL.destroyTexture texture