packages feed

swarm-0.6.0.0: src/swarm-tui/Swarm/TUI/Model/Popup.hs

{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE TemplateHaskell #-}

module Swarm.TUI.Model.Popup (
  -- * Popup types
  Popup (..),

  -- * Popup state
  PopupState,
  currentPopup,
  popupQueue,
  initPopupState,
  addPopup,

  -- * Popup animation
  popupFrames,
  progressPopups,
) where

import Control.Lens (makeLenses, use, (%~), (+=), (.=), _2, _Just)
import Control.Monad.State (MonadState)
import Data.Functor (($>))
import Data.Maybe (isJust)
import Data.Sequence (Seq, (|>), pattern (:<|))
import Data.Sequence qualified as Seq
import Swarm.Game.Achievement.Definitions (CategorizedAchievement)
import Swarm.Language.Syntax (Const)

-- | Different types of popups that can be displayed to the
--   player.
data Popup
  = AchievementPopup CategorizedAchievement
  | RecipesPopup
  | CommandsPopup [Const]

-- | State to track pending popup queue as well as any
--   popup which is currently being displayed.
data PopupState = PopupState
  { _currentPopup :: Maybe (Popup, Int)
  , _popupQueue :: Seq Popup
  }

makeLenses ''PopupState

-- | Initial, empty popup state.
initPopupState :: PopupState
initPopupState =
  PopupState
    { _currentPopup = Nothing
    , _popupQueue = Seq.empty
    }

-- | Add a popup to the end of the queue.
addPopup :: Popup -> PopupState -> PopupState
addPopup notif = popupQueue %~ (|> notif)

-- | The number of frames for which to display a popup.
popupFrames :: Int
popupFrames = 100

-- | Progress the popup state by one frame: pull the next
--   popup from the queue if there is no current popup
--   or the current popup has reached the max frame count;
--   otherwise just increment the frame count of the current
--   popup.
--
--   Return True if something was updated that might require redrawing
--   the UI.
progressPopups :: MonadState PopupState m => m Bool
progressPopups = do
  cur <- use currentPopup
  case cur of
    Nothing -> nextPopup
    Just (_, frameCount)
      | frameCount == popupFrames -> nextPopup $> True
      | otherwise -> do
          currentPopup . _Just . _2 += 1
          pure True

-- | Move the next popup (if any) from the queue to the
--   currently displayed popup.  Return True if there was any
--   popup to move.
nextPopup :: MonadState PopupState m => m Bool
nextPopup = do
  q <- use popupQueue
  cur <- use currentPopup
  case q of
    Seq.Empty
      | isJust cur -> do
          currentPopup .= Nothing
          pure True
      | otherwise -> pure False
    n :<| ns -> do
      currentPopup .= Just (n, 0)
      popupQueue .= ns
      pure True