packages feed

termbox-tea-0.1.0: examples/Demo.hs

module Main (main) where

import Control.Concurrent (threadDelay)
import Control.Concurrent.MVar
import Control.Monad (forever)
import GHC.Clock (getMonotonicTime)
import qualified Ki
import qualified Termbox.Tea as Termbox
import Text.Printf (printf)

main :: IO ()
main = do
  t0 <- getMonotonicTime
  result <-
    Ki.scoped \scope -> do
      timeVar <- newEmptyMVar
      Ki.fork_ scope do
        forever do
          threadDelay 100_000
          getMonotonicTime >>= putMVar timeVar
      Termbox.run
        Termbox.Program
          { initialize,
            pollEvent = pollEvent t0 timeVar,
            handleEvent,
            render,
            finished
          }
  case result of
    Left err -> print err
    Right _state -> pure ()

data State = State
  { elapsed :: Double,
    lastKey :: Maybe Termbox.Key,
    bright :: Bool
  }

initialize :: Termbox.Size -> State
initialize _size =
  State
    { elapsed = 0,
      lastKey = Nothing,
      bright = False
    }

pollEvent :: Double -> MVar Double -> Maybe (IO Double)
pollEvent t0 timeVar =
  Just do
    t1 <- takeMVar timeVar
    pure (t1 - t0)

handleEvent :: State -> Termbox.Event Double -> IO State
handleEvent State {elapsed, lastKey, bright} = \case
  Termbox.EventKey key ->
    pure
      State
        { elapsed,
          lastKey = Just key,
          bright =
            case key of
              Termbox.KeyChar '*' -> not bright
              _ -> bright
        }
  Termbox.EventUser elapsed1 ->
    pure
      State
        { elapsed = elapsed1,
          lastKey,
          bright
        }
  _ ->
    pure
      State
        { elapsed,
          lastKey,
          bright
        }

render :: State -> Termbox.Scene
render State {elapsed, lastKey, bright} =
  renderBox Termbox.Pos {row = 1, col = 2} $
    vcat
      [ string "Welcome to Termbox. Try typing, clicking, and resizing the terminal!",
        string " ",
        hcat
          [ string "Elapsed time: ",
            string (map Termbox.char (printf "%.1fs" elapsed))
          ],
        hcat
          [ string "Latest key press: ",
            case lastKey of
              Nothing -> emptyBox
              Just event -> string (map (Termbox.bold . Termbox.char) (show event))
          ],
        string " ",
        string "default red green yellow blue magenta cyan white",
        hcat
          [ rect
              Rect
                { size = Termbox.Size {width = 7, height = 2},
                  color = brighten Termbox.defaultColor
                },
            string " ",
            rect
              Rect
                { size = Termbox.Size {width = 3, height = 2},
                  color = brighten Termbox.red
                },
            string " ",
            rect
              Rect
                { size = Termbox.Size {width = 5, height = 2},
                  color = brighten Termbox.green
                },
            string " ",
            rect
              Rect
                { size = Termbox.Size {width = 6, height = 2},
                  color = brighten Termbox.yellow
                },
            string " ",
            rect
              Rect
                { size = Termbox.Size {width = 4, height = 2},
                  color = brighten Termbox.blue
                },
            string " ",
            rect
              Rect
                { size = Termbox.Size {width = 7, height = 2},
                  color = brighten Termbox.magenta
                },
            string " ",
            rect
              Rect
                { size = Termbox.Size {width = 4, height = 2},
                  color = brighten Termbox.cyan
                },
            string " ",
            rect
              Rect
                { size = Termbox.Size {width = 5, height = 2},
                  color = brighten Termbox.white
                },
            string " ",
            vcat
              [ string ("Press " ++ [Termbox.bold (Termbox.char '*')] ++ " to toggle brightness."),
                string
                  ( let selected = map (Termbox.bg (Termbox.gray 20) . Termbox.fg (Termbox.gray 0))
                     in if bright
                          then "normal " ++ selected "bright"
                          else selected "normal" ++ " bright"
                  )
              ]
          ],
        string " ",
        string "color 0 .. color 215",
        vcat
          ( map
              ( \is ->
                  hcat
                    ( map
                        ( \i ->
                            acat
                              [ rect
                                  Rect
                                    { size = Termbox.Size {width = 4, height = 2},
                                      color = Termbox.color i
                                    },
                                string (map (Termbox.bg (Termbox.color i) . Termbox.char) (show i))
                              ]
                        )
                        is
                    )
              )
              (chunksOf 24 [0 .. 215])
          ),
        string " ",
        string "gray 0 .. gray 23",
        hcat
          ( map
              ( \i ->
                  acat
                    [ rect
                        Rect
                          { size = Termbox.Size {width = 4, height = 2},
                            color = Termbox.gray i
                          },
                      string (map (Termbox.bg (Termbox.gray i) . Termbox.char) (show i))
                    ]
              )
              [0 .. 23]
          ),
        string " ",
        hcat
          [ string (map Termbox.bold "This text is bold."),
            string " ",
            string (map Termbox.underline "This text is underlined."),
            string " ",
            string (map Termbox.blink "This text is blinking (maybe).")
          ],
        string " ",
        hcat
          [ string "Press ",
            string (map Termbox.bold "Esc"),
            string " to quit!"
          ]
      ]
  where
    brighten :: Termbox.Color -> Termbox.Color
    brighten =
      if bright
        then Termbox.bright
        else id

finished :: State -> Bool
finished State {lastKey} =
  case lastKey of
    Just Termbox.KeyEsc -> True
    _ -> False

data Box
  = Box !Termbox.Size Content

data Content
  = E
  | O !Termbox.Cell
  | A !Box !Box
  | H !Box !Box
  | V !Box !Box

emptyBox :: Box
emptyBox =
  Box (Termbox.Size 0 0) E

one :: Termbox.Cell -> Box
one cell =
  Box (Termbox.Size 1 1) (O cell) -- assume it's not an empty cell *shrug*

acat :: [Box] -> Box
acat =
  foldr f emptyBox
  where
    f :: Box -> Box -> Box
    f box1@(Box (Termbox.Size w1 h1) _) box2@(Box (Termbox.Size w2 h2) _) =
      Box (Termbox.Size (max w1 w2) (max h1 h2)) (A box1 box2)

hcat :: [Box] -> Box
hcat =
  foldr f emptyBox
  where
    f :: Box -> Box -> Box
    f box1@(Box (Termbox.Size w1 h1) _) box2@(Box (Termbox.Size w2 h2) _) =
      Box (Termbox.Size (w1 + w2) (max h1 h2)) (H box1 box2)

vcat :: [Box] -> Box
vcat =
  foldr f emptyBox
  where
    f :: Box -> Box -> Box
    f box1@(Box (Termbox.Size w1 h1) _) box2@(Box (Termbox.Size w2 h2) _) =
      Box (Termbox.Size (max w1 w2) (h1 + h2)) (V box1 box2)

renderBox :: Termbox.Pos -> Box -> Termbox.Scene
renderBox pos (Box _ content) =
  case content of
    E -> mempty
    O cell -> Termbox.cell pos cell
    A box1 box2 -> renderBox pos box1 <> renderBox pos box2
    H box1@(Box (Termbox.Size w1 _) _) box2 -> renderBox pos box1 <> renderBox (Termbox.posRight w1 pos) box2
    V box1@(Box (Termbox.Size _ h1) _) box2 -> renderBox pos box1 <> renderBox (Termbox.posDown h1 pos) box2

string :: [Termbox.Cell] -> Box
string =
  hcat . map one

data Rect = Rect
  { size :: Termbox.Size,
    color :: Termbox.Color
  }

rect :: Rect -> Box
rect Rect {size = Termbox.Size {width, height}, color} =
  vcat (replicate height (string (replicate width (Termbox.bg color (Termbox.char ' ')))))

chunksOf :: Int -> [a] -> [[a]]
chunksOf n = \case
  [] -> []
  xs ->
    let (ys, zs) = splitAt n xs
     in ys : chunksOf n zs