-
Notifications
You must be signed in to change notification settings - Fork 59
/
Vty.hs
179 lines (167 loc) · 6.45 KB
/
Vty.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
-- | Vty supports input and output to terminal devices.
--
-- - Input to the terminal is provided to the app as a sequence of 'Event's.
--
-- - The output is defined by a 'Picture'. Which is one or more layers of 'Image's.
--
-- - The module "Graphics.Vty.Image" provides a number of constructor equations that will build
-- correct 'Image' values. See 'string', '<|>', and '<->' for starters.
--
-- - The constructors in "Graphics.Vty.Image.Internal" should not be used.
--
-- - 'Image's can be styled using 'Attr'. See "Graphics.Vty.Attributes".
--
-- See the vty-examples package for a number of examples.
--
-- @
-- main = do
-- vty <- 'mkVty' def
-- let line0 = 'string' (def `withForeColor` 'green') \"first line\"
-- line1 = 'string' (def `withBackColor` 'blue') \"second line\"
-- img = line0 '<->' line1
-- pic = 'picForImage' img
-- 'update' vty pic
-- e :: 'Event' <- 'nextEvent' vty
-- 'shutdown' vty
-- print $ \"Last event was: \" ++ show e
-- @
--
-- Good sources of documentation for terminal programming are:
--
-- - <https://github.com/b4winckler/vim/blob/master/src/term.c>
--
-- - <http://invisible-island.net/xterm/ctlseqs/ctlseqs.html>
--
-- - <http://ulisse.elettra.trieste.it/services/doc/serial/config.html>
--
-- - <http://www.leonerd.org.uk/hacks/hints/xterm-8bit.html>
--
-- - <http://www.unixwiz.net/techtips/termios-vmin-vtime.html>
--
-- - <http://vt100.net/docs/vt100-ug/chapter3.html vt100 control sequences>
module Graphics.Vty ( Vty(..)
, mkVty
, module Graphics.Vty.Config
, module Graphics.Vty.Input
, module Graphics.Vty.Output
, module Graphics.Vty.Picture
, DisplayRegion
)
where
import Graphics.Vty.Prelude
import Graphics.Vty.Config
import Graphics.Vty.Input
import Graphics.Vty.Output
import Graphics.Vty.Picture
import Control.Concurrent
import Data.IORef
import Data.Monoid
-- | The main object. At most one should be created.
--
-- The use of Vty typically follows this process:
--
-- 0. initialize vty
--
-- 1. use the update equation of Vty to display a picture
--
-- 2. repeat
--
-- 3. shutdown vty.
--
-- An alternative to tracking the Vty instance is to use 'withVty' in "Graphics.Vty.Inline.Unsafe".
--
-- This does not assure any thread safety. In theory, as long as an update action is not executed
-- when another update action is already then it's safe to call this on multiple threads.
--
-- \todo Remove explicit `shutdown` requirement.
data Vty = Vty
{ -- | Outputs the given Picture. Equivalent to 'outputPicture' applied to a display context
-- implicitly managed by Vty. The managed display context is reset on resize.
update :: Picture -> IO ()
-- | Get one Event object, blocking if necessary. This will refresh the terminal if the event
-- is a 'EvResize'.
, nextEvent :: IO Event
-- | The input interface. See 'Input'
, inputIface :: Input
-- | The output interface. See 'Output'
, outputIface :: Output
-- | Refresh the display. 'nextEvent' will refresh the display if a resize occurs.
-- If other programs output to the terminal and mess up the display then the application might
-- want to force a refresh.
, refresh :: IO ()
-- | Clean up after vty.
-- The above methods will throw an exception if executed after this is executed.
, shutdown :: IO ()
}
-- | Set up the state object for using vty. At most one state object should be
-- created at a time for a given terminal device.
--
-- The specified config is added to the 'userConfig'. With the 'userConfig' taking precedence.
-- See "Graphics.Vty.Config"
--
-- For most applications @mkVty def@ is sufficient.
mkVty :: Config -> IO Vty
mkVty appConfig = do
config <- mappend <$> pure appConfig <*> userConfig
input <- inputForCurrentTerminal config
out <- outputForCurrentTerminal
intMkVty input out
intMkVty :: Input -> Output -> IO Vty
intMkVty input out = do
reserveDisplay out
let shutdownIo = do
shutdownInput input
releaseDisplay out
releaseTerminal out
lastPicRef <- newIORef Nothing
lastUpdateRef <- newIORef Nothing
let innerUpdate inPic = do
b@(w,h) <- displayBounds out
let cursor = picCursor inPic
inPic' = case cursor of
Cursor x y ->
let
x' = case x of
_ | x < 0 -> 0
| x >= w -> w - 1
| otherwise -> x
y' = case y of
_ | y < 0 -> 0
| y >= h -> h - 1
| otherwise -> y
in inPic { picCursor = Cursor x' y' }
_ -> inPic
mlastUpdate <- readIORef lastUpdateRef
updateData <- case mlastUpdate of
Nothing -> do
dc <- displayContext out b
outputPicture dc inPic'
return (b, dc)
Just (lastBounds, lastContext) -> do
if b /= lastBounds
then do
dc <- displayContext out b
outputPicture dc inPic'
return (b, dc)
else do
outputPicture lastContext inPic'
return (b, lastContext)
writeIORef lastUpdateRef $ Just updateData
writeIORef lastPicRef $ Just inPic'
let innerRefresh
= writeIORef lastUpdateRef Nothing
>> readIORef lastPicRef
>>= maybe ( return () ) ( \pic -> innerUpdate pic )
let gkey = do k <- readChan $ _eventChannel input
case k of
(EvResize _ _) -> innerRefresh
>> displayBounds out
>>= return . (\(w,h)-> EvResize w h)
_ -> return k
return $ Vty { update = innerUpdate
, nextEvent = gkey
, inputIface = input
, outputIface = out
, refresh = innerRefresh
, shutdown = shutdownIo
}