1 -- This file is part of Intricacy
2 -- Copyright (C) 2013 Martin Bays <mbays@sdf.org>
4 -- This program is free software: you can redistribute it and/or modify
5 -- it under the terms of version 3 of the GNU General Public License as
6 -- published by the Free Software Foundation, or any later version.
8 -- You should have received a copy of the GNU General Public License
9 -- along with this program. If not, see http://www.gnu.org/licenses/.
13 import qualified UI
.HSCurses
.Curses
as Curses
14 import Control
.Concurrent
.STM
15 import Control
.Applicative
16 import qualified Data
.Map
as Map
22 import Control
.Monad
.Trans
.Maybe
23 import Control
.Monad
.State
24 import Data
.Function
(on
)
27 import GameState
(stateBoard
)
39 data UIState
= UIState
40 { dispCPairs
::[Curses
.Pair
]
42 , dispLastCol
::PieceColouring
43 , uiKeyBindings
:: Map InputMode KeyBindings
45 , message
::Maybe (Curses
.Attr
, ColPair
, String)}
46 type UIM
= StateT UIState
IO
47 nullUIState
= UIState
[] (PHS zero
) Map
.empty Map
.empty False Nothing
49 readBindings
:: UIM
()
50 readBindings
= void
.runMaybeT
$ do
51 path
<- liftIO
$ confFilePath
"bindings"
52 bdgs
<- MaybeT
$ liftIO
$ readReadFile path
53 lift
$ modify
$ \s
-> s
{uiKeyBindings
= bdgs
}
54 writeBindings
:: UIM
()
56 path
<- liftIO
$ confFilePath
"bindings"
57 bdgs
<- gets uiKeyBindings
59 liftIO
$ writeFile path
$ show bdgs
61 getBindings
:: InputMode
-> UIM KeyBindings
63 uibdgs
<- Map
.findWithDefault
[] mode
<$> gets uiKeyBindings
64 return $ uibdgs
++ bindings mode
66 bindingsStr
:: InputMode
-> [Command
] -> UIM
String
67 bindingsStr mode cmds
= do
68 bdgs
<- getBindings mode
69 return $ (("["++).(++"]")) $ intercalate
"," $
70 map (maybe "" showKey
. findBinding bdgs
) cmds
73 data Gravity
= GravUp | GravLeft | GravRight | GravDown | GravCentre
74 deriving (Eq
, Ord
, Show, Enum
)
76 data Draw
= Draw
{ drawWidth
:: Int, doDraw
:: CVec
-> UIM
() }
78 doDrawAt
:: CVec
-> Draw
-> UIM
()
79 doDrawAt
= flip doDraw
81 alignDraw
:: Gravity
-> Int -> Draw
-> Draw
82 alignDraw gravity w
(Draw w
' d
) = Draw
(max w w
') $ \(CVec y x
) ->
83 d
$ CVec y
$ x
+ shift
85 shift
= case gravity
of
87 GravRight
-> max 0 $ w
- w
'
88 _
-> max 0 . (`
div`
2) $ w
- w
'
90 instance Monoid Draw
where
91 mempty
= Draw
0 (const $ return ())
92 mappend
(Draw w d
) (Draw w
' d
') =
93 Draw
(w
+w
') $ \cpos
@(CVec y x
) -> d cpos
>> d
' (CVec y
(x
+w
))
95 stringDraw
:: Curses
.Attr
-> ColPair
-> String -> Draw
96 stringDraw attr col str
=
97 Draw
(length str
) $ \cpos
-> drawStr attr col cpos str
99 greyDraw
:: String -> Draw
100 greyDraw
= stringDraw a0 white
102 bindingsDraw
:: KeyBindings
-> [Command
] -> Draw
103 bindingsDraw
= bindingsDrawColour white
104 bindingsDrawColour
:: ColPair
-> KeyBindings
-> [Command
] -> Draw
105 bindingsDrawColour col bdgs cmds
=
106 mconcat
. ((stringDraw a0 col
"[") :) . (++ [stringDraw a0 col
"]"]) .
107 intersperse (stringDraw a0 col
",") $
108 catMaybes $ ((keyDraw
<$>) . findBinding bdgs
) <$> cmds
110 keyDraw
= stringDraw bold col
. showKeyFriendlyShort
112 data BindingsEntry
= BindingsEntry
String [Command
]
114 drawBindingsTables
:: InputMode
-> Frame
-> UIM
()
115 drawBindingsTables mode frame | mode `
elem`
[ IMEdit
, IMPlay
] = do
116 bdgs
<- getBindings mode
117 (h
,w
) <- liftIO Curses
.scrSize
118 let startRight
= frameWidth frame
+ 3
119 let maxWidth
= (w `
div`
2) - startRight
- 1
120 let entryDraws
(BindingsEntry desc cmds
) =
121 (greyDraw desc
, bindingsDraw bdgs cmds
)
122 forM_
[GravLeft
, GravRight
] $ \grav
->
123 let table
= bindingsTable mode grav
124 drawsTable
= map (\(line
, entry
) -> (line
, entryDraws entry
)) table
125 maxDesc
= maximum $ map (drawWidth
. fst . snd) drawsTable
126 maxBdgs
= maximum $ map (drawWidth
. snd . snd) drawsTable
127 descX
= (w `
div`
2) + if grav
== GravRight
128 then startRight
+ maxBdgs
+ 2
129 else -(startRight
+ maxBdgs
+ 2 + maxDesc
)
130 bdgsX
= (w `
div`
2) + if grav
== GravRight
132 else -(startRight
+ maxBdgs
)
133 oppGrav
= if grav
== GravRight
then GravLeft
else GravRight
134 useDescs
= maxDesc
+ 1 + maxBdgs
<= maxWidth
137 when (maxBdgs
<= maxWidth
) $
138 doDrawAt
(CVec y bdgsX
) $ alignDraw
139 (if useDescs
then grav
else oppGrav
) maxBdgs
142 doDrawAt
(CVec y descX
) $ alignDraw oppGrav maxDesc descDraw
143 |
(yoff
, (descDraw
, bdgsDraw
)) <- drawsTable
144 , let y
= (h `
div`
2) + yoff
147 bindingsTable IMPlay GravLeft
=
148 [ (-5, BindingsEntry
"move tool" $
149 map (CmdDir WHSSelected
) hexDirs
)
150 , (-4, BindingsEntry
"select tool"
151 [CmdToggle
, CmdTile
$ WrenchTile zero
, CmdTile HookTile
])
152 , (-3, BindingsEntry
"move hook" $
153 map (CmdDir WHSHook
) hexDirs
)
154 , (-2, BindingsEntry
"move wrench" $
155 map (CmdDir WHSWrench
) hexDirs
)
156 , (-1, BindingsEntry
"rotate hook"
157 [CmdRotate whs dir | whs
<- [WHSSelected
, WHSHook
], dir
<- [-1,1]])
158 , ( 0, BindingsEntry
"wait" [CmdWait
])
159 , ( 2, BindingsEntry
"open lock" [CmdOpen
])
160 , ( 4, BindingsEntry
"undo, redo" [CmdUndo
, CmdRedo
])
161 , ( 5, BindingsEntry
"marks" [CmdMark
, CmdJumpMark
, CmdReset
])
163 bindingsTable IMPlay GravRight
=
164 [ (-7, BindingsEntry
"help" [CmdHelp
])
165 , (-6, BindingsEntry
"bind" [CmdBind Nothing
])
166 , (7, BindingsEntry
"quit" [CmdQuit
])
168 bindingsTable IMReplay GravLeft
=
169 [ ( 0, BindingsEntry
"wait" [CmdWait
])
170 , ( 4, BindingsEntry
"undo, redo" [CmdUndo
, CmdRedo
])
171 , ( 5, BindingsEntry
"marks" [CmdMark
, CmdJumpMark
, CmdReset
])
173 bindingsTable IMReplay GravRight
=
174 [ (-7, BindingsEntry
"help" [CmdHelp
])
175 , (-6, BindingsEntry
"bind" [CmdBind Nothing
])
176 , (7, BindingsEntry
"quit" [CmdQuit
])
178 bindingsTable IMEdit GravLeft
=
179 [ (-4, BindingsEntry
"move" $ map (CmdDir WHSSelected
) hexDirs
)
180 , (-3, BindingsEntry
"rotate"
181 [CmdRotate whs dir | whs
<- [WHSSelected
], dir
<- [-1,1]])
182 , (-1, BindingsEntry
"select" [CmdSelect
])
183 , ( 0, BindingsEntry
"delete" [CmdDelete
])
184 , ( 1, BindingsEntry
"merge" [CmdMerge
])
185 , ( 4, BindingsEntry
"undo, redo" [CmdUndo
, CmdRedo
])
186 , ( 5, BindingsEntry
"marks" [CmdMark
, CmdJumpMark
, CmdReset
])
188 bindingsTable IMEdit GravRight
=
189 [ (-7, BindingsEntry
"help" [CmdHelp
])
190 , (-6, BindingsEntry
"bind" [CmdBind Nothing
])
191 , (-4, BindingsEntry
"place" $ map CmdTile
193 , SpringTile Relaxed zero
198 , (-1, BindingsEntry
"test" [CmdTest
])
199 , ( 0, BindingsEntry
"play" [CmdPlay
])
200 , ( 1, BindingsEntry
"step" [CmdWait
])
201 , ( 4, BindingsEntry
"write" [CmdWriteState
])
202 , ( 7, BindingsEntry
"quit" [CmdQuit
])
204 bindingsTable _ _
= []
206 drawBindingsTables _ _
= return ()
208 -- |frameWidth = maximum . map (abs . cx . hexVec2CVec) .
209 -- blockPattern . placedPiece . framePiece
210 frameWidth
:: Frame
-> Int
211 frameWidth frame
@(BasicFrame size
) = max (2*size
) $
212 2*(size
+ boltWidth frame
) - (size`
div`
2)
215 erase
= liftIO Curses
.erase
217 refresh
= liftIO Curses
.refresh
219 type Geom
= (CVec
, HexPos
)
222 (h
,w
) <- liftIO Curses
.scrSize
223 centre
<- gets dispCentre
224 return (CVec
(h`
div`
2) (w`
div`
2), centre
)
226 drawAt
:: Glyph
-> HexPos
-> UIM
()
228 drawAtWithGeom gl pos
=<< getGeom
230 drawAtWithGeom
:: Glyph
-> HexPos
-> Geom
-> UIM
()
231 drawAtWithGeom gl pos geom
@(scrCentre
,centre
) =
232 drawAtCVec gl
$ scrCentre
+^
(hexVec2CVec
$ pos
-^ centre
)
234 drawAtCVec
:: Glyph
-> CVec
-> UIM
()
235 drawAtCVec gl cpos
= do
236 cpairs
<- gets dispCPairs
237 liftIO
$ mvAddGlyph cpairs cpos gl
239 drawStr
:: Curses
.Attr
-> ColPair
-> CVec
-> String -> UIM
()
240 drawStr attr col v str
= do
241 cpairs
<- gets dispCPairs
242 liftIO
$ Curses
.attrSet attr
(cpairs
!!col
) >> mvAddStr v str
243 drawStrGrey
:: CVec
-> String -> UIM
()
244 drawStrGrey
= drawStr a0
0
245 drawStrCentred
:: Curses
.Attr
-> ColPair
-> CVec
-> [Char] -> UIM
()
246 drawStrCentred attr col v str
=
247 drawStr attr col
(truncateCVec
$ (v
+^ CVec
0 (-length str `
div`
2))) str
250 drawCursorAt
:: Maybe HexPos
-> UIM
()
251 drawCursorAt Nothing
=
252 void
$ liftIO
$ Curses
.cursSet Curses
.CursorInvisible
253 drawCursorAt
(Just pos
) = do
254 geom
@(scrCentre
,centre
) <- getGeom
255 liftIO
$ Curses
.cursSet Curses
.CursorVisible
256 liftIO
$ move
$ scrCentre
+^
(hexVec2CVec
$ pos
-^ centre
)
258 drawState
:: [PieceIdx
] -> Bool -> [Alert
] -> GameState
-> UIM
()
259 drawState reversed colourFixed alerts st
= do
260 lastCol
<- gets dispLastCol
261 colouring
<- drawStateWithGeom reversed colourFixed lastCol st
=<< getGeom
262 modify
$ \ds
-> ds
{ dispLastCol
= colouring
}
264 drawStateWithGeom
:: [PieceIdx
] -> Bool -> PieceColouring
-> GameState
-> Geom
-> UIM PieceColouring
265 drawStateWithGeom reversed colourFixed lastCol st geom
= do
266 let colouring
= boardColouring st
(colouredPieces colourFixed st
) lastCol
267 mono
<- gets monochrome
268 sequence_ [ drawAtWithGeom glyph pos geom |
269 (pos
,glyph
) <- Map
.toList
$ fmap (ownedTileGlyph mono colouring reversed
) $ stateBoard st
273 drawMsgLine
= void
.runMaybeT
$ do
274 (attr
,col
,str
) <- MaybeT
$ gets message
276 (h
,w
) <- liftIO Curses
.scrSize
277 liftIO
$ clearLine
$ h
-1
278 let str
' = take (w
-1) str
279 drawStr attr col
(CVec
(h
-1) 0) str
'
280 setMsgLine attr col str
= do
281 modify
$ \s
-> s
{ message
= Just
(attr
,col
,str
) }
285 drawTitle
(Just title
) = do
286 (h
,w
) <- liftIO Curses
.scrSize
287 drawStrCentred a0 white
(CVec
0 (w`
div`
2)) title
288 drawTitle Nothing
= return ()
290 say
= setMsgLine bold white
291 sayError
= setMsgLine bold red