1{-# LANGUAGE FlexibleContexts #-}
2{-# LANGUAGE FlexibleInstances #-}
4{-# LANGUAGE LambdaCase #-}
5{-# LANGUAGE MultiParamTypeClasses #-}
6{-# LANGUAGE OverloadedStrings #-}
7{-# LANGUAGE RankNTypes #-}
8{-# LANGUAGE RecursiveDo #-}
9{-# LANGUAGE ScopedTypeVariables #-}
10{-# LANGUAGE TupleSections #-}
11{-# OPTIONS_GHC -threaded #-}
13import Control.Applicative
15import Control.Monad.Fix
18import qualified Data.Map as Map
19import Data.Text (Text)
20import qualified Data.Text as T
21import qualified Data.Text.Zipper as TZ
22import qualified Graphics.Vty as V
24import Reflex.Class.Orphans
26import Reflex.Class.Switchable
27import Reflex.NotReady.Class
30data Example = Example_TextEditor
33 deriving (Show, Read, Eq, Ord, Enum, Bounded)
35div' :: (Integral a, Applicative f) => f a -> f a -> f a
45 let buttons = col $ do
47 fixed 1 $ text "Select an example."
48 fixed 1 $ text "Esc will bring you back here."
49 fixed 1 $ text "Ctrl+c to quit."
50 a <- fixed 5 $ textButtonStatic def "Todo List"
51 b <- fixed 5 $ textButtonStatic def "Text Editor"
53 [ Left Example_Todo <$ a
54 , Left Example_TextEditor <$ b
59 return $ fforMaybe i $ \case
60 V.EvKey V.KEsc [] -> Just $ Right ()
62 rec out <- networkHold buttons $ ffor (switch (current out)) $ \case
63 Left Example_TextEditor -> escapable testBoxes
64 Left Example_Todo -> escapable taskList
66 return $ fforMaybe inp $ \case
67 V.EvKey (V.KChar 'c') [V.MCtrl] -> Just ()
71 :: (Reflex t, MonadHold t m, MonadFix m, Adjustable t m, NotReady t m, PostBuild t m)
74 let btn = textButtonStatic def "Add another task"
76 rec let todos' = todos [Todo "Find reflex-vty" True, Todo "Become functional reactive" False, Todo "Make vty apps" False] $ leftmost
78 , fforMaybe inp $ \case
79 V.EvKey V.KEnter [] -> Just ()
82 (m, (e, _)) <- splitV (pure (subtract 6)) (pure (True, True)) todos' $ do
83 splitV (pure (subtract 3)) (pure (True, True)) btn (display $ current m)
86testBoxes :: (Reflex t, MonadHold t m, MonadFix m) => VtyWidget t m ()
90 let region1 = DynRegion (div' dw 6) (div' dh 6) (div' dw 2) (div' dh 2)
91 region2 = DynRegion (div' dw 4) (div' dh 4) (2 * div' dw 3) (2 * div' dh 3)
92 pane region1 (constDyn False) . boxStatic singleBoxStyle $ debugInput
93 _ <- pane region2 (constDyn True) . boxStatic singleBoxStyle $
94 splitVDrag (hRule doubleBoxStyle) (boxStatic roundedBoxStyle $ multilineTextInput def) (boxStatic roundedBoxStyle dragTest)
97debugFocus :: (Reflex t, Monad m) => VtyWidget t m ()
100 text $ T.pack . show <$> current f
102debugInput :: (Reflex t, MonadHold t m) => VtyWidget t m ()
104 lastEvent <- hold "No event yet" . fmap show =<< input
105 text $ T.pack <$> lastEvent
107dragTest :: (Reflex t, MonadHold t m, MonadFix m) => VtyWidget t m ()
109 lastEvent <- hold "No event yet" . fmap show =<< drag V.BLeft
110 text $ T.pack <$> lastEvent
112testStringBox :: (Reflex t, Monad m) => VtyWidget t m ()
113testStringBox = boxStatic singleBoxStyle .
114 text . pure . T.pack . take 500 $ cycle ('\n' : ['a'..'z'])
117 { _todo_label :: Text
120 deriving (Show, Read, Eq, Ord)
122data TodoOutput t = TodoOutput
123 { _todoOutput_todo :: Dynamic t Todo
124 , _todoOutput_delete :: Event t ()
125 , _todoOutput_height :: Dynamic t Int
128instance Reflex t => Switchable t (TodoOutput t) where
129 switching t0 e = TodoOutput
130 <$> switching (_todoOutput_todo t0) (_todoOutput_todo <$> e)
131 <*> switching (_todoOutput_delete t0) (_todoOutput_delete <$> e)
132 <*> switching (_todoOutput_height t0) (_todoOutput_height <$> e)
135 :: (MonadHold t m, MonadFix m, Reflex t)
137 -> VtyWidget t m (TodoOutput t)
140 rec let checkboxWidth = 3
141 checkboxRegion = DynRegion 0 0 checkboxWidth 1
142 labelHeight = _textInput_lines ti
143 labelWidth = w - 1 - checkboxWidth
144 labelLeft = checkboxWidth + 1
145 labelTop = constDyn 0
146 labelRegion = DynRegion labelLeft labelTop labelWidth labelHeight
147 value <- pane checkboxRegion (pure True) $ checkbox def $ _todo_done t0
148 (ti, d) <- pane labelRegion (pure True) $ do
150 v <- textInput $ def { _textInputConfig_initialValue = TZ.fromText $ _todo_label t0 }
151 let deleteSelf = attachWithMaybe backspaceOnEmpty (current $ _textInput_value v) i
152 return (v, deleteSelf)
154 { _todoOutput_todo = Todo <$> (_textInput_value ti) <*> value
155 , _todoOutput_delete = d
156 , _todoOutput_height = _textInput_lines ti
159 backspaceOnEmpty v = \case
160 V.EvKey V.KBS _ | T.null v -> Just ()
164 :: forall t m. (MonadHold t m, MonadFix m, Reflex t, Adjustable t m, NotReady t m, PostBuild t m)
167 -> VtyWidget t m (Dynamic t (Map Int Todo))
168todos todos0 newTodo = do
169 let todosMap0 = Map.fromList $ zip [0..] todos0
171 rec listOut <- listHoldWithKey todosMap0 updates $ \row t -> do
172 let reg = zipDynWith (\w' ts ->
173 let l = Map.size $ Map.takeWhileAntitone (<row) ts
174 in Region 0 l w' 1) w todosMap
175 pane (DynRegion 0 (fromMaybe 0 . Map.lookup row <$> offsets) w (fromMaybe 1 . Map.lookup row <$> heights))
176 (fmap (==row) selected) $ do
180 let heights :: Dynamic t (Map Int Int) = joinDynThroughMap $ fmap (fmap (_todoOutput_height . snd)) listOut
181 offsets :: Dynamic t (Map Int Int) = ffor heights $ \m ->
183 o = scanl (+) 0 $ map snd l
184 in Map.fromList $ zipWith (\(k, _) o' -> (k, o')) l o
185 let selectionClick = switch . current $ fmap (leftmost . Map.elems . fmap fst) listOut
186 selected <- holdDyn 0 $ leftmost
188 , fmapMaybe (fmap fst . Map.lookupMax) insert
191 let todosMap = joinDynThroughMap $ fmap (fmap (_todoOutput_todo . snd)) listOut
192 todoDelete = switch . current $
193 leftmost . Map.elems . Map.mapWithKey (\k -> (k <$) . _todoOutput_delete . snd) <$> listOut
194 selectOnDelete = attachWithMaybe
195 (\m k -> let (before, after) = Map.split k m
196 in fmap fst $ Map.lookupMax before <|> Map.lookupMin after)
199 insert = ffor (tag (current todosMap) newTodo) $ \m -> case Map.lookupMax m of
200 Nothing -> Map.singleton 0 $ Just $ Todo "" False
201 Just (k, _) -> Map.singleton (k+1) $ Just $ Todo "" False
202 delete = ffor todoDelete $ \k -> Map.singleton k Nothing
203 updates = leftmost [insert, delete]
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list