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/.
11 {-# LANGUAGE FlexibleInstances, FlexibleContexts #-}
12 {-# OPTIONS_GHC -cpp #-}
13 module SDLUIMInstance
() where
15 import Graphics
.UI
.SDL
hiding (flip, name
)
16 import qualified Graphics
.UI
.SDL
as SDL
17 import qualified Graphics
.UI
.SDL
.TTF
as TTF
18 import Control
.Concurrent
.STM
19 import Control
.Applicative
21 import qualified Data
.Map
as Map
23 import qualified Data
.Vector
as Vector
25 import Control
.Concurrent
(threadDelay
)
26 import Control
.Monad
.State
27 import Control
.Monad
.Trans
.Maybe
28 import Control
.Monad
.Trans
.Reader
32 import Data
.Function
(on
)
33 import Data
.Foldable
(for_
)
34 --import Debug.Trace (traceShow)
54 instance UIMonad
(StateT UIState
IO) where
55 runUI m
= evalStateT m nullUIState
57 lift
$ clearButtons
>> clearSelectables
62 lift
. drawTitle
=<< getTitle
65 drawUIOptionButtons mode
66 gets needHoverUpdate
>>?
do
68 modify
(\ds
-> ds
{needHoverUpdate
=False})
70 drawShortMouseHelp mode
73 drawMainState
' (PlayState
{ psCurrentState
=st
, psLastAlerts
=alerts
,
74 wrenchSelected
=wsel
, psIsTut
=isTut
, psSolved
=solved
}) = do
75 canUndo
<- null <$> gets psGameStateMoveStack
76 canRedo
<- null <$> gets psUndoneStack
78 let selTools
= [ idx |
79 (idx
, PlacedPiece pos p
) <- enumVec
$ placedPieces st
80 , or [wsel
&& isWrench p
, not wsel
&& isHook p
] ]
81 drawMainGameState selTools
False alerts st
82 lb
<- isJust <$> gets leftButtonDown
83 rb
<- isJust <$> gets leftButtonDown
85 centre
<- gets dispCentre
87 [ registerSelectable
(pos
-^ centre
) 0 $
88 if isWrench p
then SelToolWrench
else SelToolHook
89 | PlacedPiece pos p
<- Vector
.toList
$ placedPieces st
93 registerUndoButtons canUndo canRedo
94 registerButton
(periphery
0) CmdOpen
(if solved
then 2 else 0) $
95 [("open", hu
+^neg hw
)] ++ if solved
&& isTut
96 then [("Click-->",9*^neg hu
)]
98 drawMainState
' (ReplayState
{ rsCurrentState
=st
, rsLastAlerts
=alerts
} ) = do
99 canUndo
<- null <$> gets rsGameStateMoveStack
100 canRedo
<- null <$> gets rsMoveStack
102 drawMainGameState
[] False alerts st
103 registerUndoButtons canUndo canRedo
104 renderToMain
$ drawCursorAt Nothing
105 drawMainState
' (EditState
{ esGameStateStack
=(st
:sts
), esUndoneStack
=undostack
,
106 selectedPiece
=selPiece
, selectedPos
=selPos
}) = lift
$ do
107 drawMainGameState
(maybeToList selPiece
) True [] st
108 renderToMain
$ drawCursorAt
$ if isNothing selPiece
then Just selPos
else Nothing
109 registerUndoButtons
(null sts
) (null undostack
)
110 when (isJust selPiece
) $ mapM_ registerButtonGroup
111 [ singleButton
(periphery
2 +^
3*^hw
+^hv
) CmdDelete
0 [("delete",hu
+^neg hw
)]
112 , singleButton
(periphery
2 +^
3*^hw
) CmdMerge
1 [("merge",hu
+^neg hw
)]
115 [ when (null . filter (pred . placedPiece
) . Vector
.toList
$ placedPieces st
)
116 $ registerButton
(periphery
0 +^ d
) cmd
2 [("place",hu
+^neg hw
),(tool
,hu
+^neg hv
)]
117 |
(pred,tool
,cmd
,d
) <- [
118 (isWrench
, "wrench", CmdTile
$ WrenchTile zero
, (-4)*^hv
+^ hw
),
119 (isHook
, "hook", CmdTile
$ HookTile
, (-3)*^hv
+^ hw
) ] ]
121 drawMainState
' (MetaState
{curServer
=saddr
, undeclareds
=undecls
,
122 cacheOnly
=cOnly
, curAuth
=auth
, codenameStack
=names
,
123 randomCodenames
=rnamestvar
, retiredLocks
=mretired
, curLockPath
=path
,
124 curLock
=mlock
, listOffset
=offset
, asyncCount
=count
}) = do
125 let ourName
= authUser
<$> auth
126 let selName
= listToMaybe names
127 let home
= isJust ourName
&& ourName
== selName
128 lift
$ renderToMain
$ (erase
>> drawCursorAt Nothing
)
130 smallFont
<- gets dispFontSmall
131 renderToMain
$ withFont smallFont
$ renderStrColAtLeft purple
132 (saddrStr saddr
++ if cOnly
then " (offline mode)" else "")
135 when (length names
> 1) $ lift
$ registerButton
136 (codenamePos
+^ neg hu
+^
2*^hw
) CmdBackCodename
0 [("back",3*^hw
)]
139 name
<- MaybeT
(return selName
)
140 FetchedRecord fresh err muirc
<- lift
$ getUInfoFetched
300 name
141 pending
<- ((>0) <$>) $ liftIO
$ atomically
$ readTVar count
144 unless ((fresh
&& not pending
) || cOnly
) $ do
145 smallFont
<- gets dispFontSmall
146 let str
= if pending
then "(response pending)" else "(updating)"
147 renderToMain
$ withFont smallFont
$
148 renderStrColBelow
(opaquify
$ dim errorCol
) str
$ codenamePos
149 maybe (return ()) (setMsgLineNoRefresh errorCol
) err
150 when (fresh
&& (isNothing ourName ||
isNothing muirc || home
)) $
151 let reg
= isNothing muirc ||
isJust ourName
152 in registerButton
(codenamePos
+^
2*^hu
)
153 (if reg
then CmdRegister
$ isJust ourName
else CmdAuth
)
154 (if isNothing ourName
then 2 else 0)
155 [(if reg
then "reg" else "auth", 3*^hw
)]
156 (if isJust muirc
then drawName
else drawNullName
) name codenamePos
157 lift
$ registerSelectable codenamePos
0 (SelSelectedCodeName name
)
158 drawRelScore name
(codenamePos
+^hu
)
159 when (isJust muirc
) $ lift
$
160 registerButton retiredPos CmdShowRetired
5 [("retired",hu
+^neg hw
)]
161 for_ muirc
$ \(RCUserInfo
(_
,uinfo
)) -> case mretired
of
164 (map (locksPos
+^
) $ zero
:[rotate n
$ 4*^hu
-^
4*^hw | n
<- [0,2,3,5]])
165 [ \pos
-> (lift
$ registerSelectable pos
1 (SelOldLock ls
)) >> drawOldLock ls pos
167 lift
$ registerButton
(retiredPos
+^ hv
) (CmdPlayLockSpec Nothing
) 1 [("play",hu
+^neg hw
),("no.",hu
+^neg hv
)]
169 sequence_ [ drawLockInfo
(ActiveLock
(codename uinfo
) i
) mlockinfo |
170 (i
,mlockinfo
) <- assocs $ userLocks uinfo
]
171 when (isJust $ msum $ elems $ userLocks uinfo
) $ lift
$ do
172 registerButton interactButtonsPos
(CmdSolve Nothing
) 2 [("solve",hu
+^neg hw
),("lock",hu
+^neg hv
)]
173 when (isJust ourName
) $
174 registerButton
(interactButtonsPos
+^hw
) (CmdViewSolution Nothing
) 1 [("view",hu
+^neg hw
),("soln",hu
+^neg hv
)]
177 lift
.renderToMain
$ renderStrColAt messageCol
178 "Home" (codenamePos
+^hw
+^neg hv
)
179 unless (null undecls
) $ do
180 lift
.renderToMain
$ renderStrColAtLeft messageCol
"Undeclared:" (undeclsPos
+^
2*^hv
+^neg hu
)
181 lift
$ registerButton
(undeclsPos
+^hw
+^neg hu
) (CmdDeclare Nothing
) 2 [("decl",hv
+^
4*^neg hu
),("soln",hw
+^
4*^neg hu
)]
182 fillArea
(undeclsPos
+^hv
)
183 (map (undeclsPos
+^
) $ hexDisc
1 ++ [hu
+^neg hw
, neg hu
+^hv
])
184 [ \pos
-> (lift
$ registerSelectable pos
0 (SelUndeclared undecl
)) >> drawActiveLock al pos
185 | undecl
@(Undeclared _ _ al
) <- undecls
]
187 maybe (drawEmptyMiniLock miniLockPos
)
188 (\lock
-> drawMiniLock lock miniLockPos
)
190 registerSelectable miniLockPos
1 SelOurLock
191 registerButton
(miniLockPos
+^
3*^neg hw
+^
2*^hu
) CmdEdit
2
192 [("edit",hu
+^neg hw
),("lock",hu
+^neg hv
)]
193 registerButton lockLinePos CmdSelectLock
1 []
194 lift
$ when (not $ null path
) $ do
195 renderToMain
$ renderStrColAtLeft messageCol
(take 16 path
) $ lockLinePos
+^ hu
196 registerSelectable
(lockLinePos
+^
2*^hu
) 1 SelLockPath
198 [ registerButton
(miniLockPos
+^
2*^neg hv
+^
2*^hu
+^ dv
) cmd
1
199 [(dirText
,hu
+^neg hw
),("lock",hu
+^neg hv
)]
200 |
(dv
,cmd
,dirText
) <- [(zero
,CmdPrevLock
,"prev"),(neg hw
,CmdNextLock
,"next")] ]
201 let tested
= maybe False (isJust.snd) mlock
202 when (isJust mlock
&& home
) $ lift
$ registerButton
203 (miniLockPos
+^
2*^neg hw
+^
3*^hu
) (CmdPlaceLock Nothing
)
204 (if tested
then 2 else 1)
205 [("place",hu
+^neg hw
),("lock",hu
+^neg hv
)]
206 rnames
<- liftIO
$ atomically
$ readTVar rnamestvar
207 unless (null rnames
) $
208 fillArea randomNamesPos
209 (map (randomNamesPos
+^
) $ hexDisc
2)
210 [ \pos
-> (lift
$ registerSelectable pos
0 (SelRandom name
)) >> drawName name pos
213 when (ourName
/= selName
) $ void
$ runMaybeT
$ do
214 when (isJust ourName
) $
215 lift
.lift
$ registerButton
(codenamePos
+^ hw
+^ neg hv
) CmdHome
1 [("home",3*^hw
)]
216 sel
<- liftMaybe selName
217 us
<- liftMaybe ourName
218 ourUInfo
<- mgetUInfo us
219 selUInfo
<- mgetUInfo sel
220 let accesses
= map (uncurry getAccessInfo
) [(ourUInfo
,selUInfo
),(selUInfo
,ourUInfo
)]
221 let posLeft
= scoresPos
+^ hw
+^ neg hu
222 let posRight
= posLeft
+^
3*^hu
223 size
<- snd <$> (lift
.lift
) getGeom
225 lift
.renderToMain
$ renderStrColAbove
(brightish white
) "ESTEEM" $ scoresPos
226 lift
$ sequence_ [ registerSelectable
(scoresPos
+^v
) 0 SelRelScore | v
<- [hv
, hv
+^hu
] ]
227 drawRelScore sel scoresPos
228 fillArea
(posLeft
+^hw
) (map (posLeft
+^
) [zero
,hw
,neg hv
])
229 [ \pos
-> (lift
$ registerSelectable pos
0 (SelScoreLock
(Just sel
) accessed
$ ActiveLock us i
)) >>
230 drawNameWithCharAndCol us white
(lockIndexChar i
) col pos
232 , let accessed
= accesses
!! 0 !! i
234 | accessed
== Just AccessedPub
= dim pubColour
235 |
(maybe False winsPoint
) accessed
= dim
$ scoreColour
$ -3
236 |
otherwise = obscure
$ scoreColour
3 ]
237 fillArea
(posRight
+^hw
) (map (posRight
+^
) [zero
,hw
,neg hv
])
238 [ \pos
-> (lift
$ registerSelectable pos
0 (SelScoreLock Nothing accessed
$ ActiveLock sel i
)) >>
239 drawNameWithCharAndCol sel white
(lockIndexChar i
) col pos
241 , let accessed
= accesses
!! 1 !! i
243 | accessed
== Just AccessedPub
= obscure pubColour
244 |
(maybe False winsPoint
) accessed
= dim
$ scoreColour
$ 3
245 |
otherwise = obscure
$ scoreColour
$ -3 ]
246 (posScore
,negScore
) <- MaybeT
$ (snd<$>) <$> getRelScoreDetails sel
247 lift
.lift
$ sequence_
249 renderToMain
$ renderStrColAt
(scoreColour score
) (sign
:show (abs score
)) pos
250 registerSelectable pos
0 SelRelScoreComponent
251 |
(sign
,score
,pos
) <-
252 [ ('-',-negScore
,posLeft
+^neg hv
+^hw
)
253 , ('+',posScore
,posRight
+^neg hv
+^hw
) ] ]
255 drawMainState
' _
= return ()
258 drawPrompt full s
= say
$ s
++ (if full
then "" else "_")
262 reportAlerts
= playAlertSounds
264 getChRaw
= resetMouseButtons
>> getChRaw
'
266 resetMouseButtons
= modify
$ \s
-> s
267 { leftButtonDown
= Nothing
268 , middleButtonDown
= Nothing
269 , rightButtonDown
= Nothing
272 events
<- liftIO getEvents
273 if not.null $ [ True | MouseButtonDown _ _ ButtonRight
<- events
]
275 else maybe getChRaw
' (return.Just
) $ listToMaybe $ [ ch
276 | KeyDown
(Keysym _ _ ch
) <- events
279 setUIBinding mode cmd ch
=
280 modify
$ \s
-> s
{ uiKeyBindings
=
281 Map
.insertWith
(\[bdg
] -> \bdgs
-> if bdg `
elem` bdgs
then delete bdg bdgs
else bdg
:bdgs
)
282 mode
[(ch
,cmd
)] $ uiKeyBindings s
}
284 getUIBinding mode cmd
= ($cmd
) <$> getBindingStr mode
286 initUI
= liftM isJust (runMaybeT
$ do
287 catchIOErrorMT
$ SDL
.init
289 [InitVideo
,InitAudio
]
293 catchIOErrorMT TTF
.init
300 liftIO
$ warpMouse
(fi
$ w`
div`
2) (fi
$ h`
div`
2)
306 catchIOErrorMT m
= MaybeT
$ liftIO
$ catchIO
(m
>> return (Just
())) (\_
-> return Nothing
)
313 unblockInput
= return $ pushEvent VideoExpose
317 impatience ticks
= do
318 liftIO
$ threadDelay
50000
319 if (ticks
>20) then do
320 let pos
= serverWaitPos
321 smallFont
<- gets dispFontSmall
323 mapM (drawAtRel
(FilledHexGlyph
$ bright black
)) [ pos
+^ i
*^hu | i
<- [0..3] ]
325 renderStrColAtLeft errorCol
("waiting..."++replicate ((ticks`
div`
5)`
mod`
3) '.') $ pos
327 registerButton
(pos
+^ neg hv
) CmdQuit
0 [("abort",hu
+^neg hw
)]
328 drawButtons IMImpatience
330 cmds
<- getInput IMImpatience
331 return $ CmdQuit `
elem` cmds
335 (scrCentre
, size
) <- getGeom
336 centre
<- gets dispCentre
337 let SVec x y
= hexVec2SVec size
(pos
-^centre
) +^ scrCentre
338 liftIO
$ warpMouse
(fi x
) (fi y
)
339 lbp
<- gets leftButtonDown
340 rbp
<- gets rightButtonDown
341 let [lbp
',rbp
'] = fmap (fmap (\_
-> (pos
-^centre
))) [lbp
,rbp
]
342 modify
$ \s
-> s
{leftButtonDown
= lbp
', rightButtonDown
= rbp
'}
345 centre
<- gets dispCentre
346 (Just
.(+^centre
).fst) <$> gets mousePos
350 registerButton
(periphery
5 +^ hw
) (CmdInputChar
'Y
') 2 []
351 registerButton
(periphery
5 +^ neg hv
) (CmdInputChar
'N
') 0 []
352 drawButtons IMTextInput
355 toggleColourMode
= modify
$ \s
-> s
{uiOptions
= (uiOptions s
){
356 useFiveColouring
= not $ useFiveColouring
$ uiOptions s
}}
360 events
<- liftIO
$ nubMouseMotions
<$> getEventsTimeout
(10^
6`
div`fps
)
361 (cmds
,uiChanged
) <- if null events
then return ([],False) else do
363 cmds
<- concat <$> mapM processEvent events
364 setPaintFromCmds cmds
366 return (cmds
,uistatesMayVisiblyDiffer oldUIState newUIState
)
367 now
<- liftIO getTicks
368 animFrameReady
<- maybe False (<now
) <$> gets nextAnimFrameAt
369 return $ cmds
++ if uiChanged || animFrameReady
then [CmdRefresh
] else []
371 nubMouseMotions evs
=
372 -- drop all but last mouse motion event
373 let nubMouseMotions
' False (mm
@(MouseMotion
{}):evs
) = mm
:(nubMouseMotions
' True evs
)
374 nubMouseMotions
' True (mm
@(MouseMotion
{}):evs
) = nubMouseMotions
' True evs
375 nubMouseMotions
' b
(ev
:evs
) = ev
:(nubMouseMotions
' b evs
)
376 nubMouseMotions
' _
[] = []
377 in reverse $ nubMouseMotions
' False $ reverse evs
378 setPaintFromCmds cmds
= sequence_
379 [ modify
$ \s
-> s
{ paintTileIndex
= pti
}
380 |
(pti
,pt
) <- zip [0..] paintTiles
382 , (isNothing pt
&& cmd
== CmdDelete
) ||
385 CmdTile t
<- Just cmd
386 guard $ ((==)`on`tileType
) t pt
') ]
388 uistatesMayVisiblyDiffer uis1 uis2
=
389 uis1
{ mousePos
= (zero
,False), lastFrameTicks
=0 }
390 /= uis2
{mousePos
= (zero
,False), lastFrameTicks
=0 }
391 processEvent
(KeyDown
(Keysym _ _ ch
)) = case mode
of
392 IMTextInput
-> return [CmdInputChar ch
]
394 setting
<- gets settingBinding
395 if isJust setting
&& ch
/= '\0'
397 modify
$ \s
-> s
{settingBinding
= Nothing
}
398 when (ch
/= '\ESC
') $ setUIBinding mode
(fromJust setting
) ch
401 uibdgs
<- Map
.findWithDefault
[] mode `
liftM` gets uiKeyBindings
402 let mCmd
= lookup ch
$ uibdgs
++ bindings mode
403 return $ maybeToList mCmd
404 processEvent
(MouseMotion
{}) = do
405 (oldMPos
,_
) <- gets mousePos
406 (pos
@(mPos
,_
),(sx
,sy
,sz
)) <- getMousePos
407 updateMousePos mode pos
408 lbp
<- gets leftButtonDown
409 rbp
<- gets rightButtonDown
410 centre
<- gets dispCentre
411 let drag
:: Maybe HexVec
-> Maybe Command
413 fromPos
@(HexVec x y z
) <- bp
414 -- check we've dragged at least a full hex's distance:
415 guard $ not.all (\(a
,b
) -> abs ((fi a
) - b
) < 1.0) $ [(x
,sx
),(y
,sy
),(z
,sz
)]
416 let dir
= hexVec2HexDirOrZero
$ mPos
-^ fromPos
418 return $ CmdDrag
(fromPos
+^centre
) dir
420 IMEdit
-> case drag rbp
of
421 Just cmd
-> return [cmd
]
422 Nothing
-> if mPos
/= oldMPos
424 pti
<- getEffPaintTileIndex
425 return $ [ CmdMoveTo
$ mPos
+^ centre
] ++
426 (if isJust lbp
then [ CmdPaintFromTo
(paintTiles
!!pti
) (oldMPos
+^centre
) (mPos
+^centre
) ] else [])
428 IMPlay
-> return $ maybeToList $ msum $ map drag
[lbp
, rbp
]
431 mouseFromTo from to
= do
432 let dir
= hexVec2HexDirOrZero
$ to
-^ from
434 then (CmdDir WHSSelected dir
:) <$> mouseFromTo
(from
+^ dir
) to
436 processEvent
(MouseButtonDown _ _ ButtonLeft
) = do
437 pos
@(mPos
,central
) <- gets mousePos
438 modify
$ \s
-> s
{ leftButtonDown
= Just mPos
}
439 rb
<- isJust <$> gets rightButtonDown
440 mcmd
<- cmdAtMousePos pos mode
(Just
False)
441 let hotspotAction
= listToMaybe
442 $ map (\cmd
-> return [cmd
]) (maybeToList mcmd
)
443 ++ [ (modify
$ \s
-> s
{paintTileIndex
= i
}) >> return []
444 | i
<- take (length paintTiles
) [0..]
445 , mPos
== paintButtonStart
+^ i
*^hv
]
446 ++ [ toggleUIOption uiOB1
>> updateHoverStr mode
>> return []
447 | mPos
== uiOptPos uiOB1
&& mode `
elem` uiOptModes uiOB1
]
448 ++ [ toggleUIOption uiOB2
>> updateHoverStr mode
>> return []
449 | mPos
== uiOptPos uiOB2
&& mode `
elem` uiOptModes uiOB2
]
450 ++ [ toggleUIOption uiOB3
>> updateHoverStr mode
>> return []
451 | mPos
== uiOptPos uiOB3
&& mode `
elem` uiOptModes uiOB3
]
452 ++ [ toggleUIOption uiOB4
>> updateHoverStr mode
>> return []
453 | mPos
== uiOptPos uiOB4
&& mode `
elem` uiOptModes uiOB4
]
454 ++ [ toggleUIOption uiOB5
>> updateHoverStr mode
>> return []
455 | mPos
== uiOptPos uiOB5
&& mode `
elem` uiOptModes uiOB5
]
457 ++ [ toggleUIOption uiOB6
>> updateHoverStr mode
>> return []
458 | mPos
== uiOptPos uiOB6
&& mode `
elem` uiOptModes uiOB6
]
462 then return [ CmdWait
]
463 else flip fromMaybe hotspotAction
$ case mode
of
465 pti
<- getEffPaintTileIndex
466 return $ [ drawCmd
(paintTiles
!!pti
) False ]
468 centre
<- gets dispCentre
469 return $ [ CmdManipulateToolAt
$ mPos
+^ centre
]
471 processEvent
(MouseButtonUp _ _ ButtonLeft
) = do
472 modify
$ \s
-> s
{ leftButtonDown
= Nothing
}
474 processEvent
(MouseButtonDown _ _ ButtonRight
) = do
475 pos
@(mPos
,_
) <- gets mousePos
476 modify
$ \s
-> s
{ rightButtonDown
= Just mPos
}
477 lb
<- isJust <$> gets leftButtonDown
479 then return [ CmdWait
]
480 else (fromMaybe [] <$>) $ runMaybeT
$ msum
482 cmd
<- MaybeT
$ cmdAtMousePos pos mode Nothing
483 guard $ mode
/= IMTextInput
484 -- modify $ \s -> s { settingBinding = Just cmd }
485 return [ CmdBind
$ Just cmd
]
487 cmd
<- MaybeT
$ cmdAtMousePos pos mode
(Just
True)
490 IMPlay
-> return [ CmdClear
, CmdWait
]
491 _
-> return [ CmdClear
, CmdSelect
] ]
492 processEvent
(MouseButtonUp _ _ ButtonRight
) = do
493 modify
$ \s
-> s
{ rightButtonDown
= Nothing
}
494 return [ CmdUnselect
]
495 processEvent
(MouseButtonDown _ _ ButtonWheelUp
) = doWheel
1
496 processEvent
(MouseButtonDown _ _ ButtonWheelDown
) = doWheel
$ -1
497 processEvent
(MouseButtonDown _ _ ButtonMiddle
) = do
498 (mPos
,_
) <- gets mousePos
499 modify
$ \s
-> s
{ middleButtonDown
= Just mPos
}
500 rb
<- isJust <$> gets rightButtonDown
501 return $ if rb
then [ CmdDelete
] else []
502 processEvent
(MouseButtonUp _ _ ButtonMiddle
) = do
503 modify
$ \s
-> s
{ middleButtonDown
= Nothing
}
505 processEvent
(VideoResize w h
) = do
508 processEvent VideoExpose
= return [ CmdRefresh
]
509 processEvent Quit
= return [ CmdForceQuit
]
511 processEvent _
= return []
514 rb
<- isJust <$> gets rightButtonDown
515 mb
<- isJust <$> gets middleButtonDown
516 if ((rb || mb || mode
== IMReplay
) && mode
/= IMEdit
)
517 ||
(mb
&& mode
== IMEdit
)
518 then return [ if dw
== 1 then CmdRedo
else CmdUndo
]
519 else if mode
/= IMEdit || rb
520 then return [ CmdRotate WHSSelected dw
]
522 modify
$ \s
-> s
{ paintTileIndex
= (paintTileIndex s
+ dw
) `
mod`
(length paintTiles
) }
526 drawCmd mt
True = CmdPaint mt
527 drawCmd
(Just t
) False = CmdTile t
528 drawCmd Nothing _
= CmdDelete
530 getMousePos
:: UIM
((HexVec
,Bool),(Double,Double,Double))
532 (scrCentre
, size
) <- getGeom
533 (x
,y
,_
) <- lift getMouseState
534 let sv
= (SVec
(fi x
) (fi y
)) +^ neg scrCentre
535 let mPos
@(HexVec x y z
) = sVec2HexVec size sv
536 let (sx
,sy
,sz
) = sVec2dHV size sv
537 let isCentral
= all (\(a
,b
) -> abs ((fi a
) - b
) < 0.5) $
538 [(x
,sx
),(y
,sy
),(z
,sz
)]
539 return ((mPos
,isCentral
),(sx
,sy
,sz
))
540 updateMousePos mode newPos
= do
541 oldPos
<- gets mousePos
542 when (newPos
/= oldPos
) $ do
543 modify
$ \ds
-> ds
{ mousePos
= newPos
}
546 showHelp mode HelpPageInput
= do
547 bdgs
<- nub <$> getBindings mode
548 smallFont
<- gets dispFontSmall
551 let bdgWidth
= (screenWidthHexes
-6) `
div`
3
552 showKeys chs
= intercalate
"/" (map showKeyFriendly chs
)
553 maxkeyslen
= maximum . (0:) $ map (length.showKeys
.map fst) $ groupBy ((==) `on`
snd) bdgs
554 extraHelpStrs
= [["Mouse commands:", "Right-click on a button to set a keybinding;"]
556 IMPlay
-> ["Click on tool to select, drag to move;",
557 "Click by tool to move; right-click to wait;", "Scroll wheel to rotate hook;",
558 "Scroll wheel with right button held down to undo/redo."]
559 IMEdit
-> ["Left-click to draw selected; scroll to change selection;",
560 "Right-click on piece to select, drag to move;",
561 "While holding right-click: left-click to advance time, middle-click to delete;",
562 "Scroll wheel to rotate selected piece; scroll wheel while held down to undo/redo."]
563 IMReplay
-> ["Scroll wheel with right button held down to undo/redo."]
564 IMMeta
-> ["Left-clicking on something does most obvious thing;"
565 , "Right-clicking does second-most obvious thing."]]
568 "Basic game instructions:"
569 , "Choose [C]odename, then [R]egister it;"
570 , "select other players, and [S]olve their locks;"
571 , "go [H]ome, then [E]dit and [P]lace a lock of your own;"
572 , "you can then [D]eclare your solutions."
573 , "Make other players green by solving their locks and not letting them solve yours."]]
575 renderStrColAt cyan
"Keybindings:" $ (screenHeightHexes`
div`
4)*^
(hv
+^neg hw
)
576 let keybindingsHeight
= screenHeightHexes
- (3 + length extraHelpStrs
+ sum (map length extraHelpStrs
))
577 sequence_ [ with
$ renderStrColAtLeft messageCol
578 ( keysStr
++ ": " ++ desc
)
579 $ (x
*bdgWidth
-(screenWidthHexes
-6)`
div`
2)*^hu
+^ neg hv
+^
580 (screenHeightHexes`
div`
4 - y`
div`
2)*^
(hv
+^neg hw
) +^
582 |
((keysStr
,with
,desc
),(x
,y
)) <- zip [(keysStr
,with
,desc
)
583 |
group <- groupBy ((==) `on`
snd) $ sortBy (compare `on`
snd) bdgs
584 , let cmd
= snd $ head group
585 , let desc
= describeCommand cmd
587 , let chs
= map fst group
588 , let keysStr
= showKeys chs
589 , let with
= if True -- 3*(bdgWidth-1) < length desc + length keysStr + 1
590 then withFont smallFont
593 (map (`
divMod` keybindingsHeight
) [0..])
594 , (x
+1)*bdgWidth
< screenWidthHexes
]
595 sequence_ [ renderStrColAt
(if firstLine
then cyan
else messageCol
) str
596 $ (screenHeightHexes`
div`
4 - y`
div`
2)*^
(hv
+^neg hw
)
599 |
((str
,firstLine
),y
) <- (intercalate
[("",False)] $ (map (`
zip`
(True:repeat False)) extraHelpStrs
)) `
zip`
[(keybindingsHeight
+1)..] ]
602 showHelp IMMeta HelpPageGame
= do
603 renderToMain
$ drawBasicHelpPage
("INTRICACY",red
) (metagameHelpText
,purple
)
605 showHelp IMMeta
(HelpPageInitiated n
) = do
606 renderToMain
$ drawBasicHelpPage
("Initiation complete",purple
) (initiationHelpText n
,red
)
608 showHelp IMEdit HelpPageFirstEdit
= do
609 renderToMain
$ drawBasicHelpPage
("Your first lock:",purple
) (firstEditHelpText
,green
)
611 showHelp _ _
= return False
613 onNewMode mode
= modify
(\ds
-> ds
{needHoverUpdate
=True}) >> say
""
617 modify
$ \uiState
-> uiState
{bgSurface
=Nothing
}
619 isNothing <$> gets bgSurface
>>?
620 modify
(\uiState
-> uiState
{bgSurface
=bg
})
623 drawShortMouseHelp mode
= do
624 mwhs
<- gets
$ whsButtons
.uiOptions
625 showBT
<- showButtonText
<$> gets uiOptions
626 when (showBT
&& isNothing mwhs
) $ do
627 let helps
= shortMouseHelp mode
628 smallFont
<- gets dispFontSmall
629 renderToMain
$ withFont smallFont
$ sequence_
630 [ renderStrColAtLeft
(dim cyan
) help
631 (periphery
3 +^ neg hu
+^
(2-n
)*^hv
)
632 |
(n
,help
) <- zip [0..] helps
]
634 shortMouseHelp IMPlay
=
635 [ "LMB: select/move tool"
636 , "LMB+drag: move tool"
639 , "RMB+Wheel: undo/redo"
641 shortMouseHelp IMEdit
=
642 [ "LMB: paint; Ctrl+LMB: delete"
643 , "Wheel: set paint type"
644 , "RMB: select piece; drag to move"
645 , "RMB+LMB: wait; RMB+MMB: delete piece"
646 , "MMB+Wheel: undo/redo"
648 shortMouseHelp IMReplay
=
649 [ "Wheel: advance/regress time" ]
650 shortMouseHelp _
= []
652 -- waitEvent' : copy of SDL.Events.waitEvent, with the timeout increased
653 -- drastically to reduce CPU load when idling.
654 waitEvent
' :: IO Event
656 where loop
= do pumpEvents
659 NoEvent
-> threadDelay
10000 >> loop
667 getEventsTimeout us
= do
668 es
<- maybeToList <$> timeout us waitEvent
'
672 updateHoverStr
:: InputMode
-> UIM
()
673 updateHoverStr mode
= do
674 p
@(mPos
,isCentral
) <- gets mousePos
675 showBT
<- showButtonText
<$> gets uiOptions
676 hstr
<- runMaybeT
$ msum
677 [ MaybeT
( cmdAtMousePos p mode Nothing
) >>= lift
. describeCommandAndKeys
678 , guard showBT
>> MaybeT
(helpAtMousePos p mode
)
679 , guard (showBT
&& mode
== IMEdit
) >> msum
680 [ return $ "set paint mode: " ++ describeCommand
(paintTileCmds
!!i
)
681 | i
<- take (length paintTiles
) [0..]
682 , mPos
== paintButtonStart
+^ i
*^hv
]
683 , guard (mPos
== uiOptPos uiOB1
&& mode `
elem` uiOptModes uiOB1
) >> describeUIOptionButton uiOB1
684 , guard (mPos
== uiOptPos uiOB2
&& mode `
elem` uiOptModes uiOB2
) >> describeUIOptionButton uiOB2
685 , guard (mPos
== uiOptPos uiOB3
&& mode `
elem` uiOptModes uiOB3
) >> describeUIOptionButton uiOB3
686 , guard (mPos
== uiOptPos uiOB4
&& mode `
elem` uiOptModes uiOB4
) >> describeUIOptionButton uiOB4
687 , guard (mPos
== uiOptPos uiOB5
&& mode `
elem` uiOptModes uiOB5
) >> describeUIOptionButton uiOB5
689 , guard (mPos
== uiOptPos uiOB6
&& mode `
elem` uiOptModes uiOB6
) >> describeUIOptionButton uiOB6
692 modify
$ \ds
-> ds
{ hoverStr
= hstr
}
694 describeCommandAndKeys
:: Command
-> UIM
String
695 describeCommandAndKeys cmd
= do
696 uibdgs
<- Map
.findWithDefault
[] mode `
liftM` gets uiKeyBindings
697 return $ describeCommand cmd
++ " ["
698 ++ concat (intersperse ","
699 (map showKeyFriendly
$ findBindings
(uibdgs
++ bindings mode
) cmd
))
703 fillArea
:: HexVec
-> [HexVec
] -> [HexVec
-> MainStateT UIM
()] -> MainStateT UIM
()
704 fillArea centre area draws
= do
705 offset
<- gets listOffset
707 listButton cmd
= \pos
-> lift
$ registerButton pos cmd
3 []
708 draws
' = if offset
> 0 && length draws
> na
709 then listButton CmdPrevPage
:
710 drop (max 0 $ min (length draws
- (na
-1)) (na
-1 + (na
-2)*(offset
-1))) draws
712 selDraws
= if length draws
' > na
713 then take (na
-1) draws
' ++ [listButton CmdNextPage
]
715 sequence_ $ map (uncurry ($)) $
716 zip selDraws
$ sortBy (compare `on` hexVec2SVec
37) $
717 take (length selDraws
) $ sortBy
718 (compare `on`
(hexLen
. (-^centre
)))
721 drawOldLock ls pos
= void
.runMaybeT
$ msum [ do
723 lift
.lift
$ drawMiniLock lock pos
724 , lift
.lift
.renderToMain
$
725 renderStrColAt messageCol
(show ls
) pos
729 drawName name pos
= nameCol name
>>= drawNameCol name pos
730 drawNullName name pos
= drawNameCol name pos
$ invisible white
731 drawNameCol name pos col
= do
732 lift
.renderToMain
$ do
733 drawAtRel
(playerGlyph col
) pos
734 renderStrColAt buttonTextCol name pos
735 drawRelScore name pos
= do
737 relScore
<- getRelScore name
738 flip (maybe (return ())) relScore
$ \score
->
740 renderToMain
$ renderStrColAt col
741 ((if score
> 0 then "+" else "") ++ show score
) pos
742 registerSelectable pos
0 SelRelScore
744 drawNote note pos
= case noteBehind note
of
745 Just al
-> drawActiveLock al pos
746 Nothing
-> drawPublicNote
(noteAuthor note
) pos
747 drawActiveLock al
@(ActiveLock name i
) pos
= do
748 accessed
<- accessedAL al
749 drawNameWithChar name
750 (if accessed
then accColour
else white
)
751 (lockIndexChar i
) pos
752 drawPublicNote name
=
753 drawNameWithChar name pubColour
'P
'
754 drawNameWithChar name charcol char pos
= do
756 drawNameWithCharAndCol name charcol char col pos
757 drawNameWithCharAndCol
:: String -> Pixel
-> Char -> Pixel
-> HexVec
-> MainStateT UIM
()
758 drawNameWithCharAndCol name charcol char col pos
= do
759 size
<- fi
.snd <$> lift getGeom
760 let up
= FVec
0 $ 1/2 - ylen
761 let down
= FVec
0 $ ylen
762 smallFont
<- lift
$ gets dispFontSmall
763 lift
.renderToMain
$ do
764 drawAtRel
(playerGlyph col
) pos
766 renderStrColAt buttonTextCol name pos
767 displaceRender down
$ withFont smallFont
$
768 renderStrColAt charcol
[char
] pos
770 pubColour
= colourWheel pubWheelAngle
-- ==purple
773 ourName
<- (authUser
<$>) <$> gets curAuth
774 relScore
<- getRelScore name
775 return $ dim
$ case relScore
of
776 Nothing
-> Pixel
$ if ourName
== Just name
then 0xc0c0c000 else 0x80808000
777 Just score
-> scoreColour score
778 scoreColour
:: Int -> Pixel
779 scoreColour score
= Pixel
$ case score
of
788 drawLockInfo
:: ActiveLock
-> Maybe LockInfo
-> MainStateT UIM
()
789 drawLockInfo al
@(ActiveLock name idx
) Nothing
= do
790 let centre
= hw
+^neg hv
+^
7*(idx
-1)*^hu
791 lift
$ drawEmptyMiniLock centre
792 drawNameWithCharAndCol name white
(lockIndexChar idx
) (invisible white
) centre
793 ourName
<- (authUser
<$>) <$> gets curAuth
794 lift
$ registerSelectable centre
3 $ SelLockUnset
(ourName
== Just name
) al
795 drawLockInfo al
@(ActiveLock name idx
) (Just lockinfo
) = do
796 let centre
= locksPos
+^
7*(idx
-1)*^hu
797 let accessedByPos
= centre
+^
3*^
(hv
+^ neg hw
)
798 let accessedPos
= centre
+^
2*^
(hw
+^ neg hv
)
799 let notesPos
= centre
+^
3*^
(hw
+^ neg hv
)
800 ourName
<- (authUser
<$>) <$> gets curAuth
803 lock
<- mgetLock
$ lockSpec lockinfo
805 drawMiniLock lock centre
806 registerSelectable centre
3 $ SelLock al
808 drawActiveLock al centre
809 lift
$ registerSelectable centre
3 $ SelLock al
812 size
<- snd <$> lift getGeom
814 renderToMain
$ displaceRender
(FVec
1 0) $ renderStrColAt
(brightish white
) "UNLOCKED BY" $ accessedByPos
+^ hv
815 registerSelectable
(accessedByPos
+^ hv
) 0 SelPrivyHeader
816 registerSelectable
(accessedByPos
+^ hv
+^ hu
) 0 SelPrivyHeader
819 renderToMain
$ renderStrColAt pubColour
"All" accessedByPos
820 registerSelectable accessedByPos
1 SelPublicLock
821 else if null $ accessedBy lockinfo
822 then lift
.renderToMain
$ renderStrColAt dimWhiteCol
"No-one" accessedByPos
823 else fillArea accessedByPos
824 [ accessedByPos
+^ d | j
<- [0..2], i
<- [-2..3]
826 , let d
= j
*^hw
+^ i
*^hu
]
827 $ [ \pos
-> (lift
$ registerSelectable pos
0 (SelSolution note
)) >> drawNote note pos
828 | note
<- lockSolutions lockinfo
] ++
829 [ \pos
-> (lift
$ registerSelectable pos
0 (SelAccessed name
)) >> drawName name pos
830 | name
<- accessedBy lockinfo
\\ map noteAuthor
(lockSolutions lockinfo
) ]
832 undecls
<- gets undeclareds
833 case if isJust $ guard . (|| public lockinfo
) . (`
elem`
map noteAuthor
(lockSolutions lockinfo
)) =<< ourName
834 then if public lockinfo
835 then Just
(pubColour
,"Accessed!",AccessedPublic
)
836 else Just
(accColour
, "Solved!",AccessedSolved
)
837 else if any (\(Undeclared _ ls _
) -> ls
== lockSpec lockinfo
) undecls
838 then Just
(yellow
, "Undeclared",AccessedUndeclared
)
841 Just
(col
,str
,selstr
) -> lift
$ do
842 renderToMain
$ renderStrColAt col str accessedPos
843 registerSelectable accessedPos
1 (SelAccessedInfo selstr
)
845 read <- take 3 <$> getNotesReadOn lockinfo
846 unless (ourName
== Just name
) $ do
847 let readPos
= accessedPos
+^
(-3)*^hu
848 lift
.renderToMain
$ renderStrColAt
(if length read == 3 then accColour
else dimWhiteCol
)
850 when (length read == 3) $ lift
$ registerSelectable readPos
0 (SelAccessedInfo AccessedReadNotes
)
851 fillArea
(accessedPos
+^neg hu
) [ accessedPos
+^ i
*^hu | i
<- [-1..1] ]
852 $ take 3 $ [ \pos
-> (lift
$ registerSelectable pos
0 (SelReadNote note
)) >> drawNote note pos
853 | note
<- read ] ++ (repeat $ \pos
-> (lift
$ registerSelectable pos
0 SelReadNoteSlot
>>
854 renderToMain
(drawAtRel
(HollowGlyph
$ dim green
) pos
)))
857 renderToMain
$ displaceRender
(FVec
1 0) $ renderStrColAt
(brightish white
) "SECURING" $ notesPos
+^ hv
858 registerSelectable
(notesPos
+^ hv
) 0 SelNotesHeader
859 registerSelectable
(notesPos
+^ hv
+^ hu
) 0 SelNotesHeader
860 if null $ notesSecured lockinfo
861 then lift
.renderToMain
$ renderStrColAt dimWhiteCol
"None" notesPos
862 else fillArea notesPos
863 [ notesPos
+^ d | j
<- [0..2], i
<- [-2..3]
865 , let d
= j
*^hw
+^ i
*^hu
]
866 [ \pos
-> (lift
$ registerSelectable pos
0 (SelSecured note
)) >> drawActiveLock
(noteOn note
) pos
867 | note
<- notesSecured lockinfo
]
869 drawBasicHelpPage
:: (String,Pixel
) -> ([String],Pixel
) -> RenderM
()
870 drawBasicHelpPage
(title
,titleCol
) (body
,bodyCol
) = do
872 let headPos
= (screenHeightHexes`
div`
4)*^
(hv
+^neg hw
)
873 renderStrColAt titleCol title headPos
875 [ renderStrColAt bodyCol str
$
877 +^
(y`
div`
2)*^
(hw
+^neg hv
)
879 |
(y
,str
) <- zip [1..] body