termbox-banana 0.4.0 → 1.0.0
raw patch · 5 files changed
+199/−166 lines, 5 filesdep ~basedep ~reactive-bananadep ~termboxPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: base, reactive-banana, termbox
API changes (from Hackage documentation)
- Termbox.Banana: Cell :: !Char -> !Attr -> !Attr -> Cell
- Termbox.Banana: Cursor :: !Int -> !Int -> Cursor
- Termbox.Banana: EventKey :: !Key -> Event a
- Termbox.Banana: EventMouse :: !Mouse -> !Int -> !Int -> Event a
- Termbox.Banana: EventResize :: !Int -> !Int -> Event a
- Termbox.Banana: EventUser :: a -> Event a
- Termbox.Banana: MouseLeft :: Mouse
- Termbox.Banana: MouseMiddle :: Mouse
- Termbox.Banana: MouseRelease :: Mouse
- Termbox.Banana: MouseRight :: Mouse
- Termbox.Banana: MouseWheelDown :: Mouse
- Termbox.Banana: MouseWheelUp :: Mouse
- Termbox.Banana: NoCursor :: Cursor
- Termbox.Banana: PollError :: PollError
- Termbox.Banana: black :: Attr
- Termbox.Banana: data Attr
- Termbox.Banana: data Cells
- Termbox.Banana: data Cursor
- Termbox.Banana: data Event a
- Termbox.Banana: data PollError
- Termbox.Banana: instance GHC.Classes.Eq a => GHC.Classes.Eq (Termbox.Banana.Event a)
- Termbox.Banana: instance GHC.Classes.Ord a => GHC.Classes.Ord (Termbox.Banana.Event a)
- Termbox.Banana: instance GHC.Show.Show a => GHC.Show.Show (Termbox.Banana.Event a)
- Termbox.Banana: reverse :: Attr
- Termbox.Banana: set :: Int -> Int -> Cell -> Cells
- Termbox.Banana: type Program a b = (a -> IO ()) " Callback that produces a user event." -> Event (Event a) " Event stream." -> Behavior (Int, Int) " Time-varying terminal size (width, then height)." -> MomentIO (Behavior (Cells, Cursor), Event b) " The time-varying scene to render, and an event stream of arbitrary values, only the first of which is relevant, which ends the @termbox@ program and returns from 'run'."
+ Termbox.Banana: Inputs :: !Size -> !Event Key -> !Event Size -> !Event Mouse -> Inputs
+ Termbox.Banana: Mouse :: !MouseButton -> !Pos -> Mouse
+ Termbox.Banana: Outputs :: !Behavior Scene -> !Event a -> Outputs a
+ Termbox.Banana: Pos :: {-# UNPACK #-} !Int -> {-# UNPACK #-} !Int -> Pos
+ Termbox.Banana: Size :: {-# UNPACK #-} !Int -> {-# UNPACK #-} !Int -> Size
+ Termbox.Banana: [$sel:button:Mouse] :: Mouse -> !MouseButton
+ Termbox.Banana: [$sel:col:Pos] :: Pos -> {-# UNPACK #-} !Int
+ Termbox.Banana: [$sel:height:Size] :: Size -> {-# UNPACK #-} !Int
+ Termbox.Banana: [$sel:pos:Mouse] :: Mouse -> !Pos
+ Termbox.Banana: [$sel:row:Pos] :: Pos -> {-# UNPACK #-} !Int
+ Termbox.Banana: [$sel:width:Size] :: Size -> {-# UNPACK #-} !Int
+ Termbox.Banana: [done] :: Outputs a -> !Event a
+ Termbox.Banana: [initialSize] :: Inputs -> !Size
+ Termbox.Banana: [keys] :: Inputs -> !Event Key
+ Termbox.Banana: [mouses] :: Inputs -> !Event Mouse
+ Termbox.Banana: [resizes] :: Inputs -> !Event Size
+ Termbox.Banana: [scene] :: Outputs a -> !Behavior Scene
+ Termbox.Banana: bg :: Color -> Cell -> Cell
+ Termbox.Banana: blink :: Cell -> Cell
+ Termbox.Banana: bright :: Color -> Color
+ Termbox.Banana: cell :: Pos -> Cell -> Scene
+ Termbox.Banana: char :: Char -> Cell
+ Termbox.Banana: color :: Int -> Color
+ Termbox.Banana: cursor :: Pos -> Scene
+ Termbox.Banana: data Color
+ Termbox.Banana: data Inputs
+ Termbox.Banana: data MouseButton
+ Termbox.Banana: data Outputs a
+ Termbox.Banana: data Pos
+ Termbox.Banana: data Scene
+ Termbox.Banana: data Size
+ Termbox.Banana: defaultColor :: Color
+ Termbox.Banana: fg :: Color -> Cell -> Cell
+ Termbox.Banana: fill :: Color -> Scene
+ Termbox.Banana: gray :: Int -> Color
+ Termbox.Banana: pattern LeftClick :: MouseButton
+ Termbox.Banana: pattern MiddleClick :: MouseButton
+ Termbox.Banana: pattern ReleaseClick :: MouseButton
+ Termbox.Banana: pattern RightClick :: MouseButton
+ Termbox.Banana: pattern WheelDown :: MouseButton
+ Termbox.Banana: pattern WheelUp :: MouseButton
+ Termbox.Banana: posDown :: Int -> Pos -> Pos
+ Termbox.Banana: posLeft :: Int -> Pos -> Pos
+ Termbox.Banana: posRight :: Int -> Pos -> Pos
+ Termbox.Banana: posUp :: Int -> Pos -> Pos
- Termbox.Banana: blue :: Attr
+ Termbox.Banana: blue :: Color
- Termbox.Banana: bold :: Attr
+ Termbox.Banana: bold :: Cell -> Cell
- Termbox.Banana: cyan :: Attr
+ Termbox.Banana: cyan :: Color
- Termbox.Banana: green :: Attr
+ Termbox.Banana: green :: Color
- Termbox.Banana: magenta :: Attr
+ Termbox.Banana: magenta :: Color
- Termbox.Banana: red :: Attr
+ Termbox.Banana: red :: Color
- Termbox.Banana: run :: Program a b -> IO b
+ Termbox.Banana: run :: (Inputs -> MomentIO (Outputs a)) -> IO (Either InitError a)
- Termbox.Banana: underline :: Attr
+ Termbox.Banana: underline :: Cell -> Cell
- Termbox.Banana: white :: Attr
+ Termbox.Banana: white :: Color
- Termbox.Banana: yellow :: Attr
+ Termbox.Banana: yellow :: Color
Files
- CHANGELOG.md +5/−0
- LICENSE +1/−1
- README.md +0/−6
- src/Termbox/Banana.hs +174/−143
- termbox-banana.cabal +19/−16
CHANGELOG.md view
@@ -1,3 +1,8 @@+## [1.0.0] - 2022-11-03++- Rework `run` type+- Bump to `termbox-1.1.0`, whose API changes are visible here, too+ ## [0.4.0] - 2022-07-16 - Parameterize `TermboxEvent` (now `Event`) by user event
LICENSE view
@@ -1,4 +1,4 @@-Copyright 2018 Mitchell Rosen+Copyright 2018-2022 Mitchell Rosen Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
− README.md
@@ -1,6 +0,0 @@-# `termbox-banana`--[](https://github.com/mitchellwrosen/termbox-banana/actions)-[](https://hackage.haskell.org/package/termbox-banana)-[](https://www.stackage.org/lts/package/termbox-banana)-[](https://www.stackage.org/nightly/package/termbox-banana)
src/Termbox/Banana.hs view
@@ -1,171 +1,202 @@-{-# LANGUAGE PatternSynonyms #-}-{-# LANGUAGE ScopedTypeVariables #-}-+-- |+-- This module provides a @reactive-banana@ FRP interface to @termbox@, a simple C library for writing text-based user+-- interfaces: <https://github.com/termbox/termbox>+--+-- See also:+--+-- * @<https://hackage.haskell.org/package/termbox-tea termbox-tea>@, an Elm Architecture interface.+--+-- This module is intended to be imported qualified.+--+-- ==== __👉 Quick start example__+--+-- This @termbox@ program displays the number of keys pressed.+--+-- @+-- {-\# LANGUAGE DuplicateRecordFields \#-}+-- {-\# LANGUAGE ImportQualifiedPost \#-}+-- {-\# LANGUAGE LambdaCase \#-}+-- {-\# LANGUAGE OverloadedRecordDot \#-}+-- {-\# LANGUAGE OverloadedStrings \#-}+-- {-\# LANGUAGE NoFieldSelectors \#-}+--+-- import Data.Foldable (fold)+-- import Reactive.Banana ((\<\@\>))+-- import Reactive.Banana qualified as Banana+-- import Termbox.Banana qualified as Termbox+--+-- main :: IO ()+-- main =+-- Termbox.'run' network \>\>= \\case+-- Left err -\> putStrLn (\"Termbox program failed to initialize: \" ++ show err)+-- Right n -\> putStrLn (\"Pressed \" ++ show n ++ \" keys.\")+--+-- network :: Banana.MonadMoment m =\> Termbox.'Inputs' -\> m (Termbox.'Outputs' Int)+-- network inputs = do+-- keysPressed <- Banana.accumB 0 ((+ 1) \<$ inputs.keys)+-- pure+-- Termbox.'Outputs'+-- { scene = render \<$\> keysPressed,+-- done = Banana.filterJust (isDone \<$\> keysPressed \<\@\> inputs.keys)+-- }+-- where+-- isDone :: Int -\> Termbox.'Termbox.Banana.Key' -\> Maybe Int+-- isDone n = \\case+-- Termbox.'Termbox.Banana.KeyEsc' -\> Just n+-- _ -\> Nothing+--+-- render :: Int -\> Termbox.'Termbox.Banana.Scene'+-- render keysPressed =+-- fold+-- [ string+-- Termbox.'Termbox.Banana.Pos' {row = 2, col = 4}+-- (\"Number of keys pressed: \" ++ map Termbox.'Termbox.Banana.char' (show keysPressed)),+-- string+-- Termbox.'Termbox.Banana.Pos' {row = 4, col = 4}+-- (\"Press \" ++ map (Termbox.'Termbox.Banana.bold' . Termbox.'Termbox.Banana.char') \"Esc\" ++ \" to quit.\")+-- ]+--+-- string :: Termbox.'Termbox.Banana.Pos' -\> [Termbox.'Termbox.Banana.Cell'] -\> Termbox.'Termbox.Banana.Scene'+-- string pos cells =+-- foldMap (\\(i, cell) -\> Termbox.'Termbox.Banana.cell' (Termbox.'Termbox.Banana.posRight' i pos) cell) (zip [0 ..] cells)+-- @ module Termbox.Banana- ( -- * Introduction- -- $intro-- -- * Core API- Event (..),- Program,+ ( -- * Main+ Inputs (..),+ Outputs (..), run,+ Termbox.InitError (..), - -- * Re-exports from @termbox@- Termbox.black,- Termbox.blue,+ -- * Terminal contents++ -- ** Scene+ Termbox.Scene,+ Termbox.cell,+ Termbox.fill,+ Termbox.cursor,++ -- ** Cell+ Termbox.Cell,+ Termbox.char,+ Termbox.fg,+ Termbox.bg, Termbox.bold,- Termbox.cyan,+ Termbox.underline,+ Termbox.blink,++ -- ** Colors+ Termbox.Color,++ -- *** Basic colors+ Termbox.defaultColor,+ Termbox.red, Termbox.green,+ Termbox.yellow,+ Termbox.blue, Termbox.magenta,- Termbox.red,- Termbox.reverse,- Termbox.underline,+ Termbox.cyan, Termbox.white,- Termbox.yellow,- Termbox.set,- Termbox.Attr,- Termbox.Cell (..),- Termbox.Cells,- Termbox.Cursor (..),- Termbox.InitError (..),+ Termbox.bright,++ -- *** 216 miscellaneous colors+ Termbox.color,++ -- *** 24 monochrome colors+ Termbox.gray,++ -- * Event handling Termbox.Key (..),- pattern Termbox.KeyCtrl2,- pattern Termbox.KeyCtrl3,- pattern Termbox.KeyCtrl4,- pattern Termbox.KeyCtrl5,- pattern Termbox.KeyCtrl7,- pattern Termbox.KeyCtrlH,- pattern Termbox.KeyCtrlI,- pattern Termbox.KeyCtrlLsqBracket,- pattern Termbox.KeyCtrlM,- pattern Termbox.KeyCtrlUnderscore, Termbox.Mouse (..),- Termbox.PollError (..),+ Termbox.MouseButton (..),++ -- * Miscellaneous types+ Termbox.Pos (..),+ Termbox.posUp,+ Termbox.posDown,+ Termbox.posLeft,+ Termbox.posRight,+ Termbox.Size (..), ) where import Control.Concurrent.MVar import Control.Monad.IO.Class (liftIO)-import Data.Function (fix)+import Data.Void (Void) import qualified Reactive.Banana as Banana import qualified Reactive.Banana.Frameworks as Banana import qualified Termbox --- $intro------ This module is intended to be imported qualified:------ @--- import qualified Termbox.Banana as Termbox--- @------ ==== __👉 Quick start example__------ This is a program that displays the last key pressed, and quits on @Esc@:------ @--- {-\# LANGUAGE LambdaCase \#-}------ module Main where------ import Data.Void (Void)--- import Reactive.Banana--- import Reactive.Banana.Frameworks--- import qualified Termbox.Banana as Termbox------ main :: IO ()--- main =--- Termbox.'run' moment------ moment--- :: (Void -> IO ())--- -> Event (Termbox.'Event' Void)--- -> Behavior (Int, Int)--- -> MomentIO (Behavior (Termbox.'Termbox.Cells', Termbox.'Termbox.Cursor'), Event ())--- moment _fireUserEvent eEvent _bSize = do--- let eQuit = () <$ filterE isKeyEsc eEvent--- bLatestEvent <- stepper Nothing (Just \<$\> eEvent)--- let bCells = maybe mempty renderEvent \<$\> bLatestEvent--- let bScene = (,) \<$\> bCells \<*\> pure Termbox.'Termbox.NoCursor'--- pure (bScene, eQuit)+-- | The inputs to a @termbox@ FRP network.+data Inputs = Inputs+ { -- | The initial terminal size.+ initialSize :: !Termbox.Size,+ -- | Key events.+ keys :: !(Banana.Event Termbox.Key),+ -- | Resize events.+ resizes :: !(Banana.Event Termbox.Size),+ -- | Mouse events.+ mouses :: !(Banana.Event Termbox.Mouse)+ }++-- | The outputs from a @termbox@ FRP network.+data Outputs a = Outputs+ { -- | The scene to render.+ scene :: !(Banana.Behavior Termbox.Scene),+ -- | The events of arbitrary values, on the first of which is relevant, which causes 'run' to return.+ --+ -- /Note/: Wrapping this event in 'Banana.once' is not necessary, as this library does so internally.+ done :: !(Banana.Event a)+ }++-- | Run a @termbox@ FRP network. ----- renderEvent :: Show a => Termbox.'Event' a -> Termbox.'Termbox.Cells'--- renderEvent =--- foldMap (\\(i, c) -> Termbox.set i 0 (Termbox.'Termbox.Cell' c mempty mempty))--- . zip [0..]--- . show+-- @run@ either: ----- isKeyEsc :: Termbox.'Event' a -> Bool--- isKeyEsc = \\case--- Termbox.'EventKey' Termbox.'Termbox.KeyEsc' -> True--- _ -> False--- @+-- * Returns immediately with an 'InitError'.+-- * Returns the first value emitted by @done@.+run ::+ -- | The FRP network.+ (Inputs -> Banana.MomentIO (Outputs a)) ->+ -- | The result of the FRP network.+ IO (Either Termbox.InitError a)+run program =+ Termbox.run (run_ program) --- | A key press, terminal resize, mouse click, or a user event.-data Event a- = EventKey !Termbox.Key- | EventResize !Int !Int- | EventMouse !Termbox.Mouse !Int !Int- | EventUser a- deriving stock (Eq, Ord, Show)+run_ :: (Inputs -> Banana.MomentIO (Outputs a)) -> IO a+run_ program = do+ initialSize <- Termbox.getSize -type Program a b =- -- | Callback that produces a user event.- (a -> IO ()) ->- -- | Event stream.- Banana.Event (Event a) ->- -- | Time-varying terminal size (width, then height).- Banana.Behavior (Int, Int) ->- -- | The time-varying scene to render, and an event stream of arbitrary values, only the first of which is relevant,- -- which ends the @termbox@ program and returns from 'run'.- Banana.MomentIO (Banana.Behavior (Termbox.Cells, Termbox.Cursor), Banana.Event b)+ doneVar <- newEmptyMVar+ (keysAddHandler, fireKey) <- Banana.newAddHandler+ (resizesAddHandler, fireResize) <- Banana.newAddHandler+ (mousesAddHandler, fireMouse) <- Banana.newAddHandler --- | Run a @termbox@ program.-run :: Program a b -> IO b-run program =- Termbox.run $ \initialWidth initialHeight render poll -> do- doneVar <- newEmptyMVar- (eventAddHandler, fireEvent) <- Banana.newAddHandler- (userEventAddHandler, fireUserEvent) <- Banana.newAddHandler+ network <-+ Banana.compile do+ keys <- Banana.fromAddHandler keysAddHandler+ resizes <- Banana.fromAddHandler resizesAddHandler+ mouses <- Banana.fromAddHandler mousesAddHandler - network <-- Banana.compile $ do- eEvent <- Banana.fromAddHandler eventAddHandler- eUserEvent <- Banana.fromAddHandler userEventAddHandler- let eTermboxEvent =- Banana.unionWith- const- ( ( \case- Termbox.EventKey key -> EventKey key- Termbox.EventResize width height -> EventResize width height- Termbox.EventMouse mouse col row -> EventMouse mouse col row- )- <$> eEvent- )- (EventUser <$> eUserEvent)+ Outputs {scene, done} <- program Inputs {initialSize, keys, resizes, mouses}+ let render = Termbox.render <$> scene - let eResize :: Banana.Event (Int, Int)- eResize =- Banana.filterJust- ( ( \case- Termbox.EventResize w h -> Just (w, h)- _ -> Nothing- )- <$> eEvent- )+ -- Render the first scene, and again every time it changes.+ liftIO =<< Banana.valueB render+ Banana.reactimate' =<< Banana.changes render - bSize <- Banana.stepper (initialWidth, initialHeight) eResize- (bScene, eDone) <- program fireUserEvent eTermboxEvent bSize- let bRender = (\(cells, cursor) -> render cells cursor) <$> bScene- eRender <- Banana.changes bRender- do- action <- Banana.valueB bRender- liftIO action- Banana.reactimate (putMVar doneVar <$> eDone)- Banana.reactimate' eRender+ -- Smuggle `done` values out via `doneVar` (only the first matters)+ done1 <- Banana.once done+ Banana.reactimate (putMVar doneVar <$> done1) - Banana.actuate network+ Banana.actuate network - fix $ \loop -> do- poll >>= fireEvent- tryReadMVar doneVar >>= maybe loop pure+ let loop = do+ Termbox.poll @Void >>= \case+ Termbox.EventKey key -> fireKey key+ Termbox.EventResize size -> fireResize size+ Termbox.EventMouse mouse -> fireMouse mouse+ tryReadMVar doneVar >>= \case+ Nothing -> loop+ Just result -> pure result++ loop
termbox-banana.cabal view
@@ -1,27 +1,27 @@ cabal-version: 2.4 -name: termbox-banana-version: 0.4.0+author: Mitchell Rosen+bug-reports: https://github.com/mitchellwrosen/termbox-banana/issues+build-type: Simple category: User Interfaces+copyright: (c) 2018-2022, Mitchell Rosen description:- A @reactive-banana@-based interface to writing @termbox@ programs.+ This package provides a @reactive-banana@ FRP interface to @termbox@ programs. .- See also the <https://hackage.haskell.org/termbox termbox> package for a- lower-level, imperative interface.-synopsis: reactive-banana + termbox-author: Mitchell Rosen-maintainer: Mitchell Rosen <mitchellwrosen@gmail.com>+ See also:+ .+ * @<https://hackage.haskell.org/package/termbox-tea termbox-tea>@ for an Elm Architecture interface. homepage: https://github.com/mitchellwrosen/termbox-banana-bug-reports: https://github.com/mitchellwrosen/termbox-banana/issues-copyright: (c) 2018-2022, Mitchell Rosen license: BSD-3-Clause license-file: LICENSE-build-type: Simple-tested-with: GHC ==8.10.7, GHC ==9.0.2, GHC ==9.2.1+maintainer: Mitchell Rosen <mitchellwrosen@gmail.com>+name: termbox-banana+synopsis: termbox + reactive-banana+tested-with: GHC == 9.0.2, GHC == 9.2.4, GHC == 9.4.2+version: 1.0.0 extra-source-files: CHANGELOG.md- README.md source-repository head type: git@@ -29,14 +29,17 @@ library build-depends:- base ^>= 4.11 || ^>= 4.12 || ^>= 4.13 || ^>= 4.14 || ^>= 4.15 || ^>= 4.16,- reactive-banana ^>= 1.2 || ^>= 1.3,- termbox ^>= 0.3,+ base ^>= 4.13 || ^>= 4.14 || ^>= 4.15 || ^>= 4.16 || ^>= 4.17,+ reactive-banana ^>= 1.3,+ termbox ^>= 1.1.0, default-extensions:+ BlockArguments DerivingStrategies GeneralizedNewtypeDeriving InstanceSigs LambdaCase+ NamedFieldPuns+ TypeApplications default-language: Haskell2010 exposed-modules: Termbox.Banana ghc-options: