module Draw.Messages where import Brick import Brick.Widgets.Border import Control.Monad (foldM) import Control.Monad.Trans.Reader (withReaderT) import qualified Data.Foldable as F import qualified Data.Map.Strict as Map import Data.Maybe (catMaybes) import Data.Monoid ((<>)) import qualified Data.Sequence as Seq import qualified Data.Text as T import Data.Time.Clock (UTCTime(..)) import qualified Graphics.Vty as Vty import Lens.Micro.Platform import Draw.Util import Markdown import Themes import Types import Types.Posts import Types.Messages maxMessageHeight :: Int maxMessageHeight = 200 renderSingleMessage :: ChatState -> UserSet -> ChannelSet -> Message -> Widget Name renderSingleMessage st uSet cSet = renderChatMessage st uSet cSet (withBrackets . renderTime st) renderChatMessage :: ChatState -> UserSet -> ChannelSet -> (UTCTime -> Widget Name) -> Message -> Widget Name renderChatMessage st uSet cSet renderTimeFunc msg = let m = renderMessage st msg True uSet cSet True msgAtch = if Seq.null (msg^.mAttachments) then Nothing else Just $ withDefAttr clientMessageAttr $ vBox [ txt (" [attached: `" <> a^.attachmentName <> "`]") | a <- F.toList (msg^.mAttachments) ] msgReac = if Map.null (msg^.mReactions) then Nothing else let renderR e 1 = " [" <> e <> "]" renderR e n | n > 1 = " [" <> e <> " " <> T.pack (show n) <> "]" | otherwise = "" reacMsg = Map.foldMapWithKey renderR (msg^.mReactions) in Just $ withDefAttr emojiAttr $ txt (" " <> reacMsg) msgTxt = case msg^.mUserName of Just _ | msg^.mType == CP Join || msg^.mType == CP Leave -> withDefAttr clientMessageAttr m | otherwise -> m Nothing -> case msg^.mType of C DateTransition -> withDefAttr dateTransitionAttr (hBorderWithLabel m) C NewMessagesTransition -> withDefAttr newMessageTransitionAttr (hBorderWithLabel m) C Error -> withDefAttr errorMessageAttr m _ -> withDefAttr clientMessageAttr m fullMsg = vBox $ msgTxt : catMaybes [msgAtch, msgReac] maybeRenderTime w = hBox [renderTimeFunc (msg^.mDate), txt " ", w] maybeRenderTimeWith f = case msg^.mType of C DateTransition -> id C NewMessagesTransition -> id _ -> f in maybeRenderTimeWith maybeRenderTime fullMsg -- | Render a selected message with focus, including the messages -- before and the messages after it. The foldable parameters exist -- because (depending on the situation) we might use either of the -- message list types for the 'before' and 'after' (i.e. the -- chronological or retrograde message sequences). unsafeRenderMessageSelection :: (Foldable f, Foldable g) => (Message, (f Message, g Message)) -> (Message -> Widget Name) -> Widget Name unsafeRenderMessageSelection (curMsg, (before, after)) doMsgRender = Widget Greedy Greedy $ do let relaxHeight c = c & availHeightL .~ (max maxMessageHeight (c^.availHeightL)) render1HLimit fjoin lim img msg | Vty.imageHeight img >= lim = return img | otherwise = fjoin img <$> render1 msg render1 msg = case msg^.mDeleted of True -> return Vty.emptyImage False -> do r <- withReaderT relaxHeight $ render $ padRight Max $ doMsgRender msg return $ r^.imageL ctx <- getContext curMsgResult <- withReaderT relaxHeight $ render $ forceAttr messageSelectAttr $ padRight Max $ doMsgRender curMsg let targetHeight = ctx^.availHeightL upperHeight = targetHeight `div` 2 lowerHeight = targetHeight - upperHeight lowerRender = render1HLimit Vty.vertJoin targetHeight upperRender = render1HLimit (flip Vty.vertJoin) targetHeight lowerHalf <- foldM lowerRender Vty.emptyImage after upperHalf <- foldM upperRender Vty.emptyImage before let curHeight = Vty.imageHeight $ curMsgResult^.imageL uncropped = upperHalf Vty.<-> curMsgResult^.imageL Vty.<-> lowerHalf img = if Vty.imageHeight lowerHalf < (lowerHeight - curHeight) then Vty.cropTop targetHeight uncropped else if Vty.imageHeight upperHalf < upperHeight then Vty.cropBottom targetHeight uncropped else Vty.cropTop upperHeight upperHalf Vty.<-> curMsgResult^.imageL Vty.<-> (if curHeight < lowerHeight then Vty.cropBottom (lowerHeight - curHeight) lowerHalf else Vty.cropBottom lowerHeight lowerHalf) return $ emptyResult & imageL .~ img