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)
53 instance UIMonad
(StateT UIState
IO) where
54 runUI m
= evalStateT m nullUIState
56 lift
$ clearButtons
>> clearSelectables
61 lift
. drawTitle
=<< getTitle
64 drawUIOptionButtons mode
65 gets needHoverUpdate
>>?
do
67 modify
(\ds
-> ds
{needHoverUpdate
=False})
69 drawShortMouseHelp mode
72 drawMainState
' (PlayState
{ psCurrentState
=st
, psLastAlerts
=alerts
, wrenchSelected
=wsel
, psIsTut
=isTut
}) = do
73 canUndo
<- null <$> gets psGameStateMoveStack
74 canRedo
<- null <$> gets psUndoneStack
76 let selTools
= [ idx |
77 (idx
, PlacedPiece pos p
) <- enumVec
$ placedPieces st
78 , or [wsel
&& isWrench p
, not wsel
&& isHook p
] ]
79 drawMainGameState selTools
False alerts st
80 lb
<- isJust <$> gets leftButtonDown
81 rb
<- isJust <$> gets leftButtonDown
83 centre
<- gets dispCentre
85 [ registerSelectable
(pos
-^ centre
) 0 $
86 if isWrench p
then SelToolWrench
else SelToolHook
87 | PlacedPiece pos p
<- Vector
.toList
$ placedPieces st
91 registerUndoButtons canUndo canRedo
92 drawMainState
' (ReplayState
{ rsCurrentState
=st
, rsLastAlerts
=alerts
} ) = do
93 canUndo
<- null <$> gets rsGameStateMoveStack
94 canRedo
<- null <$> gets rsMoveStack
96 drawMainGameState
[] False alerts st
97 registerUndoButtons canUndo canRedo
98 renderToMain
$ drawCursorAt Nothing
99 drawMainState
' (EditState
{ esGameStateStack
=(st
:sts
), esUndoneStack
=undostack
,
100 selectedPiece
=selPiece
, selectedPos
=selPos
}) = lift
$ do
101 drawMainGameState
(maybeToList selPiece
) True [] st
102 renderToMain
$ drawCursorAt
$ if isNothing selPiece
then Just selPos
else Nothing
103 registerUndoButtons
(null sts
) (null undostack
)
104 when (isJust selPiece
) $ mapM_ registerButtonGroup
105 [ singleButton
(periphery
2 +^
3*^hw
+^hv
) CmdDelete
0 [("delete",hu
+^neg hw
)]
106 , singleButton
(periphery
2 +^
3*^hw
) CmdMerge
1 [("merge",hu
+^neg hw
)]
109 [ when (null . filter (pred . placedPiece
) . Vector
.toList
$ placedPieces st
)
110 $ registerButton
(periphery
0 +^ d
) cmd
2 [("place",hu
+^neg hw
),(tool
,hu
+^neg hv
)]
111 |
(pred,tool
,cmd
,d
) <- [
112 (isWrench
, "wrench", CmdTile
$ WrenchTile zero
, (-4)*^hv
+^ hw
),
113 (isHook
, "hook", CmdTile
$ HookTile
, (-3)*^hv
+^ hw
) ] ]
115 drawMainState
' (MetaState
{curServer
=saddr
, undeclareds
=undecls
,
116 cacheOnly
=cOnly
, curAuth
=auth
, codenameStack
=names
,
117 randomCodenames
=rnamestvar
, retiredLocks
=mretired
, curLockPath
=path
,
118 curLock
=mlock
, listOffset
=offset
, asyncCount
=count
}) = do
119 let ourName
= authUser
<$> auth
120 let selName
= listToMaybe names
121 let home
= isJust ourName
&& ourName
== selName
122 lift
$ renderToMain
$ (erase
>> drawCursorAt Nothing
)
124 smallFont
<- gets dispFontSmall
125 renderToMain
$ withFont smallFont
$ renderStrColAtLeft purple
126 (saddrStr saddr
++ if cOnly
then " (cache only)" else "")
129 when (length names
> 1) $ lift
$ registerButton
130 (codenamePos
+^ neg hu
+^
2*^hw
) CmdBackCodename
0 [("back",3*^hw
)]
133 name
<- MaybeT
(return selName
)
134 FetchedRecord fresh err muirc
<- lift
$ getUInfoFetched
300 name
135 pending
<- ((>0) <$>) $ liftIO
$ atomically
$ readTVar count
138 unless ((fresh
&& not pending
) || cOnly
) $ do
139 smallFont
<- gets dispFontSmall
140 let str
= if pending
then "(response pending)" else "(updating)"
141 renderToMain
$ withFont smallFont
$
142 renderStrColBelow
(opaquify
$ dim errorCol
) str
$ codenamePos
143 maybe (return ()) (setMsgLineNoRefresh errorCol
) err
144 when (fresh
&& (isNothing ourName ||
isNothing muirc || home
)) $
145 let reg
= isNothing muirc ||
isJust ourName
146 in registerButton
(codenamePos
+^
2*^hu
)
147 (if reg
then CmdRegister
$ isJust ourName
else CmdAuth
)
148 (if isNothing ourName
then 2 else 0)
149 [(if reg
then "reg" else "auth", 3*^hw
)]
150 (if isJust muirc
then drawName
else drawNullName
) name codenamePos
151 lift
$ registerSelectable codenamePos
0 (SelSelectedCodeName name
)
152 drawRelScore name
(codenamePos
+^hu
)
153 when (isJust muirc
) $ lift
$
154 registerButton retiredPos CmdShowRetired
5 [("retired",hu
+^neg hw
)]
155 for_ muirc
$ \(RCUserInfo
(_
,uinfo
)) -> case mretired
of
158 (map (locksPos
+^
) $ zero
:[rotate n
$ 4*^hu
-^
4*^hw | n
<- [0,2,3,5]])
159 [ \pos
-> (lift
$ registerSelectable pos
1 (SelOldLock ls
)) >> drawOldLock ls pos
161 lift
$ registerButton
(retiredPos
+^ hv
) (CmdPlayLockSpec Nothing
) 1 [("play",hu
+^neg hw
),("no.",hu
+^neg hv
)]
163 sequence_ [ drawLockInfo
(ActiveLock
(codename uinfo
) i
) mlockinfo |
164 (i
,mlockinfo
) <- assocs $ userLocks uinfo
]
165 when (isJust $ msum $ elems $ userLocks uinfo
) $ lift
$ do
166 registerButton interactButtonsPos
(CmdSolve Nothing
) 2 [("solve",hu
+^neg hw
),("lock",hu
+^neg hv
)]
167 registerButton
(interactButtonsPos
+^hw
) (CmdViewSolution Nothing
) 1 [("view",hu
+^neg hw
),("soln",hu
+^neg hv
)]
170 lift
.renderToMain
$ renderStrColAt messageCol
171 "Home" (codenamePos
+^hw
+^neg hv
)
172 unless (null undecls
) $ do
173 lift
.renderToMain
$ renderStrColAtLeft messageCol
"Undeclared:" (undeclsPos
+^
2*^hv
+^neg hu
)
174 lift
$ registerButton
(undeclsPos
+^hw
+^neg hu
) (CmdDeclare Nothing
) 2 [("decl",hv
+^
4*^neg hu
),("soln",hw
+^
4*^neg hu
)]
175 fillArea
(undeclsPos
+^hv
)
176 (map (undeclsPos
+^
) $ hexDisc
1 ++ [hu
+^neg hw
, neg hu
+^hv
])
177 [ \pos
-> (lift
$ registerSelectable pos
0 (SelUndeclared undecl
)) >> drawActiveLock al pos
178 | undecl
@(Undeclared _ _ al
) <- undecls
]
180 maybe (drawEmptyMiniLock miniLockPos
)
181 (\lock
-> drawMiniLock lock miniLockPos
)
183 registerSelectable miniLockPos
1 SelOurLock
184 registerButton
(miniLockPos
+^
3*^neg hw
+^
2*^hu
) CmdEdit
2
185 [("edit",hu
+^neg hw
),("lock",hu
+^neg hv
)]
186 registerButton lockLinePos CmdSelectLock
1 []
187 lift
$ when (not $ null path
) $ do
188 renderToMain
$ renderStrColAtLeft messageCol
(take 16 path
) $ lockLinePos
+^ hu
189 registerSelectable
(lockLinePos
+^
2*^hu
) 1 SelLockPath
191 [ registerButton
(miniLockPos
+^
2*^neg hv
+^
2*^hu
+^ dv
) cmd
1
192 [(dirText
,hu
+^neg hw
),("lock",hu
+^neg hv
)]
193 |
(dv
,cmd
,dirText
) <- [(zero
,CmdPrevLock
,"prev"),(neg hw
,CmdNextLock
,"next")] ]
194 let tested
= maybe False (isJust.snd) mlock
195 when (isJust mlock
&& home
) $ lift
$ registerButton
196 (miniLockPos
+^
2*^neg hw
+^
3*^hu
) (CmdPlaceLock Nothing
)
197 (if tested
then 2 else 1)
198 [("place",hu
+^neg hw
),("lock",hu
+^neg hv
)]
199 rnames
<- liftIO
$ atomically
$ readTVar rnamestvar
200 unless (null rnames
) $
201 fillArea randomNamesPos
202 (map (randomNamesPos
+^
) $ hexDisc
2)
203 [ \pos
-> (lift
$ registerSelectable pos
0 (SelRandom name
)) >> drawName name pos
206 when (ourName
/= selName
) $ void
$ runMaybeT
$ do
207 when (isJust ourName
) $
208 lift
.lift
$ registerButton
(codenamePos
+^ hw
+^ neg hv
) CmdHome
1 [("home",3*^hw
)]
209 sel
<- liftMaybe selName
210 us
<- liftMaybe ourName
211 ourUInfo
<- mgetUInfo us
212 selUInfo
<- mgetUInfo sel
213 let accesses
= map (uncurry getAccessInfo
) [(ourUInfo
,selUInfo
),(selUInfo
,ourUInfo
)]
214 let posLeft
= scoresPos
+^ hw
+^ neg hu
215 let posRight
= posLeft
+^
3*^hu
216 size
<- snd <$> (lift
.lift
) getGeom
218 lift
.renderToMain
$ renderStrColAbove
(brightish white
) "ESTEEM" $ scoresPos
219 lift
$ sequence_ [ registerSelectable
(scoresPos
+^v
) 0 SelRelScore | v
<- [hv
, hv
+^hu
] ]
220 drawRelScore sel scoresPos
221 fillArea
(posLeft
+^hw
) (map (posLeft
+^
) [zero
,hw
,neg hv
])
222 [ \pos
-> (lift
$ registerSelectable pos
0 (SelScoreLock
(Just sel
) accessed
$ ActiveLock us i
)) >>
223 drawNameWithCharAndCol us white
(lockIndexChar i
) col pos
225 , let accessed
= accesses
!! 0 !! i
227 | accessed
== Just AccessedPub
= dim pubColour
228 |
(maybe False winsPoint
) accessed
= dim
$ scoreColour
$ -3
229 |
otherwise = obscure
$ scoreColour
3 ]
230 fillArea
(posRight
+^hw
) (map (posRight
+^
) [zero
,hw
,neg hv
])
231 [ \pos
-> (lift
$ registerSelectable pos
0 (SelScoreLock Nothing accessed
$ ActiveLock sel i
)) >>
232 drawNameWithCharAndCol sel white
(lockIndexChar i
) col pos
234 , let accessed
= accesses
!! 1 !! i
236 | accessed
== Just AccessedPub
= obscure pubColour
237 |
(maybe False winsPoint
) accessed
= dim
$ scoreColour
$ 3
238 |
otherwise = obscure
$ scoreColour
$ -3 ]
239 (posScore
,negScore
) <- MaybeT
$ (snd<$>) <$> getRelScoreDetails sel
240 lift
.lift
$ sequence_
242 renderToMain
$ renderStrColAt
(scoreColour score
) (sign
:show (abs score
)) pos
243 registerSelectable pos
0 SelRelScoreComponent
244 |
(sign
,score
,pos
) <-
245 [ ('-',-negScore
,posLeft
+^neg hv
+^hw
)
246 , ('+',posScore
,posRight
+^neg hv
+^hw
) ] ]
248 drawMainState
' _
= return ()
251 drawPrompt full s
= say
$ s
++ (if full
then "" else "_")
255 reportAlerts
= playAlertSounds
258 events
<- liftIO getEvents
259 if not.null $ [ True | MouseButtonDown _ _ ButtonRight
<- events
]
261 else maybe getChRaw
(return.Just
) $ listToMaybe $ [ ch
262 | KeyDown
(Keysym _ _ ch
) <- events
265 setUIBinding mode cmd ch
=
266 modify
$ \s
-> s
{ uiKeyBindings
=
267 Map
.insertWith
(\[bdg
] -> \bdgs
-> if bdg `
elem` bdgs
then delete bdg bdgs
else bdg
:bdgs
)
268 mode
[(ch
,cmd
)] $ uiKeyBindings s
}
270 getUIBinding mode cmd
= ($cmd
) <$> getBindingStr mode
272 initUI
= liftM isJust (runMaybeT
$ do
273 catchIOErrorMT
$ SDL
.init
275 [InitVideo
,InitAudio
]
279 catchIOErrorMT TTF
.init
286 liftIO
$ warpMouse
(fi
$ w`
div`
2) (fi
$ h`
div`
2)
292 catchIOErrorMT m
= MaybeT
$ liftIO
$ catchIO
(m
>> return (Just
())) (\_
-> return Nothing
)
299 unblockInput
= return $ pushEvent VideoExpose
303 impatience ticks
= do
304 liftIO
$ threadDelay
50000
305 if (ticks
>20) then do
306 let pos
= serverWaitPos
307 smallFont
<- gets dispFontSmall
309 mapM (drawAtRel
(FilledHexGlyph
$ bright black
)) [ pos
+^ i
*^hu | i
<- [0..3] ]
311 renderStrColAtLeft errorCol
("waiting..."++replicate ((ticks`
div`
5)`
mod`
3) '.') $ pos
313 registerButton
(pos
+^ neg hv
) CmdQuit
0 [("abort",hu
+^neg hw
)]
314 drawButtons IMImpatience
316 cmds
<- getInput IMImpatience
317 return $ CmdQuit `
elem` cmds
321 (scrCentre
, size
) <- getGeom
322 centre
<- gets dispCentre
323 let SVec x y
= hexVec2SVec size
(pos
-^centre
) +^ scrCentre
324 liftIO
$ warpMouse
(fi x
) (fi y
)
325 lbp
<- gets leftButtonDown
326 rbp
<- gets rightButtonDown
327 let [lbp
',rbp
'] = fmap (fmap (\_
-> (pos
-^centre
))) [lbp
,rbp
]
328 modify
$ \s
-> s
{leftButtonDown
= lbp
', rightButtonDown
= rbp
'}
331 centre
<- gets dispCentre
332 (Just
.(+^centre
).fst) <$> gets mousePos
336 registerButton
(periphery
5 +^ hw
) (CmdInputChar
'Y
') 2 []
337 registerButton
(periphery
5 +^ neg hv
) (CmdInputChar
'N
') 0 []
338 drawButtons IMTextInput
341 toggleColourMode
= modify
$ \s
-> s
{uiOptions
= (uiOptions s
){
342 useFiveColouring
= not $ useFiveColouring
$ uiOptions s
}}
346 events
<- liftIO
$ nubMouseMotions
<$> getEventsTimeout
(10^
6`
div`fps
)
347 (cmds
,uiChanged
) <- if null events
then return ([],False) else do
349 cmds
<- concat <$> mapM processEvent events
350 setPaintFromCmds cmds
352 return (cmds
,uistatesMayVisiblyDiffer oldUIState newUIState
)
353 now
<- liftIO getTicks
354 animFrameReady
<- maybe False (<now
) <$> gets nextAnimFrameAt
355 return $ cmds
++ if uiChanged || animFrameReady
then [CmdRefresh
] else []
357 nubMouseMotions evs
=
358 -- drop all but last mouse motion event
359 let nubMouseMotions
' False (mm
@(MouseMotion
{}):evs
) = mm
:(nubMouseMotions
' True evs
)
360 nubMouseMotions
' True (mm
@(MouseMotion
{}):evs
) = nubMouseMotions
' True evs
361 nubMouseMotions
' b
(ev
:evs
) = ev
:(nubMouseMotions
' b evs
)
362 nubMouseMotions
' _
[] = []
363 in reverse $ nubMouseMotions
' False $ reverse evs
364 setPaintFromCmds cmds
= sequence_
365 [ modify
$ \s
-> s
{ paintTileIndex
= pti
}
366 |
(pti
,pt
) <- zip [0..] paintTiles
368 , (isNothing pt
&& cmd
== CmdDelete
) ||
371 CmdTile t
<- Just cmd
372 guard $ ((==)`on`tileType
) t pt
') ]
374 uistatesMayVisiblyDiffer uis1 uis2
=
375 uis1
{ mousePos
= (zero
,False), lastFrameTicks
=0 }
376 /= uis2
{mousePos
= (zero
,False), lastFrameTicks
=0 }
377 processEvent
(KeyDown
(Keysym _ _ ch
)) = case mode
of
378 IMTextInput
-> return [CmdInputChar ch
]
380 setting
<- gets settingBinding
381 if isJust setting
&& ch
/= '\0'
383 modify
$ \s
-> s
{settingBinding
= Nothing
}
384 when (ch
/= '\ESC
') $ setUIBinding mode
(fromJust setting
) ch
387 uibdgs
<- Map
.findWithDefault
[] mode `
liftM` gets uiKeyBindings
388 let mCmd
= lookup ch
$ uibdgs
++ bindings mode
389 return $ maybeToList mCmd
390 processEvent
(MouseMotion
{}) = do
391 (oldMPos
,_
) <- gets mousePos
392 (pos
@(mPos
,_
),(sx
,sy
,sz
)) <- getMousePos
393 updateMousePos mode pos
394 lbp
<- gets leftButtonDown
395 rbp
<- gets rightButtonDown
396 centre
<- gets dispCentre
397 let drag
:: Maybe HexVec
-> Maybe Command
399 fromPos
@(HexVec x y z
) <- bp
400 -- check we've dragged at least a full hex's distance:
401 guard $ not.all (\(a
,b
) -> abs ((fi a
) - b
) < 1.0) $ [(x
,sx
),(y
,sy
),(z
,sz
)]
402 let dir
= hexVec2HexDirOrZero
$ mPos
-^ fromPos
404 return $ CmdDrag
(fromPos
+^centre
) dir
406 IMEdit
-> case drag rbp
of
407 Just cmd
-> return [cmd
]
408 Nothing
-> if mPos
/= oldMPos
410 pti
<- getEffPaintTileIndex
411 return $ [ CmdMoveTo
$ mPos
+^ centre
] ++
412 (if isJust lbp
then [ CmdPaintFromTo
(paintTiles
!!pti
) (oldMPos
+^centre
) (mPos
+^centre
) ] else [])
414 IMPlay
-> return $ maybeToList $ msum $ map drag
[lbp
, rbp
]
417 mouseFromTo from to
= do
418 let dir
= hexVec2HexDirOrZero
$ to
-^ from
420 then (CmdDir WHSSelected dir
:) <$> mouseFromTo
(from
+^ dir
) to
422 processEvent
(MouseButtonDown _ _ ButtonLeft
) = do
423 pos
@(mPos
,central
) <- gets mousePos
424 modify
$ \s
-> s
{ leftButtonDown
= Just mPos
}
425 rb
<- isJust <$> gets rightButtonDown
426 mcmd
<- cmdAtMousePos pos mode
(Just
False)
427 let hotspotAction
= listToMaybe
428 $ map (\cmd
-> return [cmd
]) (maybeToList mcmd
)
429 ++ [ (modify
$ \s
-> s
{paintTileIndex
= i
}) >> return []
430 | i
<- take (length paintTiles
) [0..]
431 , mPos
== paintButtonStart
+^ i
*^hv
]
432 ++ [ toggleUIOption uiOB1
>> updateHoverStr mode
>> return []
433 | mPos
== uiOptPos uiOB1
&& mode `
elem` uiOptModes uiOB1
]
434 ++ [ toggleUIOption uiOB2
>> updateHoverStr mode
>> return []
435 | mPos
== uiOptPos uiOB2
&& mode `
elem` uiOptModes uiOB2
]
436 ++ [ toggleUIOption uiOB3
>> updateHoverStr mode
>> return []
437 | mPos
== uiOptPos uiOB3
&& mode `
elem` uiOptModes uiOB3
]
438 ++ [ toggleUIOption uiOB4
>> updateHoverStr mode
>> return []
439 | mPos
== uiOptPos uiOB4
&& mode `
elem` uiOptModes uiOB4
]
440 ++ [ toggleUIOption uiOB5
>> updateHoverStr mode
>> return []
441 | mPos
== uiOptPos uiOB5
&& mode `
elem` uiOptModes uiOB5
]
443 ++ [ toggleUIOption uiOB6
>> updateHoverStr mode
>> return []
444 | mPos
== uiOptPos uiOB6
&& mode `
elem` uiOptModes uiOB6
]
448 then return [ CmdWait
]
449 else flip fromMaybe hotspotAction
$ case mode
of
451 pti
<- getEffPaintTileIndex
452 return $ [ drawCmd
(paintTiles
!!pti
) False ]
454 centre
<- gets dispCentre
455 return $ [ CmdManipulateToolAt
$ mPos
+^ centre
]
457 processEvent
(MouseButtonUp _ _ ButtonLeft
) = do
458 modify
$ \s
-> s
{ leftButtonDown
= Nothing
}
460 processEvent
(MouseButtonDown _ _ ButtonRight
) = do
461 pos
@(mPos
,_
) <- gets mousePos
462 modify
$ \s
-> s
{ rightButtonDown
= Just mPos
}
463 lb
<- isJust <$> gets leftButtonDown
465 then return [ CmdWait
]
466 else (fromMaybe [] <$>) $ runMaybeT
$ msum
468 cmd
<- MaybeT
$ cmdAtMousePos pos mode Nothing
469 guard $ mode
/= IMTextInput
470 -- modify $ \s -> s { settingBinding = Just cmd }
471 return [ CmdBind
$ Just cmd
]
473 cmd
<- MaybeT
$ cmdAtMousePos pos mode
(Just
True)
476 IMPlay
-> return [ CmdClear
, CmdWait
]
477 _
-> return [ CmdClear
, CmdSelect
] ]
478 processEvent
(MouseButtonUp _ _ ButtonRight
) = do
479 modify
$ \s
-> s
{ rightButtonDown
= Nothing
}
480 return [ CmdUnselect
]
481 processEvent
(MouseButtonDown _ _ ButtonWheelUp
) = doWheel
1
482 processEvent
(MouseButtonDown _ _ ButtonWheelDown
) = doWheel
$ -1
483 processEvent
(MouseButtonDown _ _ ButtonMiddle
) = do
484 (mPos
,_
) <- gets mousePos
485 modify
$ \s
-> s
{ middleButtonDown
= Just mPos
}
486 rb
<- isJust <$> gets rightButtonDown
487 return $ if rb
then [ CmdDelete
] else []
488 processEvent
(MouseButtonUp _ _ ButtonMiddle
) = do
489 modify
$ \s
-> s
{ middleButtonDown
= Nothing
}
491 processEvent
(VideoResize w h
) = do
494 processEvent VideoExpose
= return [ CmdRefresh
]
495 processEvent Quit
= return [ CmdForceQuit
]
497 processEvent _
= return []
500 rb
<- isJust <$> gets rightButtonDown
501 mb
<- isJust <$> gets middleButtonDown
502 if ((rb || mb || mode
== IMReplay
) && mode
/= IMEdit
)
503 ||
(mb
&& mode
== IMEdit
)
504 then return [ if dw
== 1 then CmdRedo
else CmdUndo
]
505 else if mode
/= IMEdit || rb
506 then return [ CmdRotate WHSSelected dw
]
508 modify
$ \s
-> s
{ paintTileIndex
= (paintTileIndex s
+ dw
) `
mod`
(length paintTiles
) }
512 drawCmd mt
True = CmdPaint mt
513 drawCmd
(Just t
) False = CmdTile t
514 drawCmd Nothing _
= CmdDelete
516 getMousePos
:: UIM
((HexVec
,Bool),(Double,Double,Double))
518 (scrCentre
, size
) <- getGeom
519 (x
,y
,_
) <- lift getMouseState
520 let sv
= (SVec
(fi x
) (fi y
)) +^ neg scrCentre
521 let mPos
@(HexVec x y z
) = sVec2HexVec size sv
522 let (sx
,sy
,sz
) = sVec2dHV size sv
523 let isCentral
= all (\(a
,b
) -> abs ((fi a
) - b
) < 0.5) $
524 [(x
,sx
),(y
,sy
),(z
,sz
)]
525 return ((mPos
,isCentral
),(sx
,sy
,sz
))
526 updateMousePos mode newPos
= do
527 oldPos
<- gets mousePos
528 when (newPos
/= oldPos
) $ do
529 modify
$ \ds
-> ds
{ mousePos
= newPos
}
532 showHelp mode HelpPageInput
= do
533 bdgs
<- nub <$> getBindings mode
534 smallFont
<- gets dispFontSmall
537 let bdgWidth
= (screenWidthHexes
-6) `
div`
3
538 showKeys chs
= intercalate
"/" (map showKeyFriendly chs
)
539 maxkeyslen
= maximum . (0:) $ map (length.showKeys
.map fst) $ groupBy ((==) `on`
snd) bdgs
540 extraHelpStrs
= [["Mouse commands:", "Right-click on a button to set a keybinding;"]
542 IMPlay
-> ["Click on tool to select, drag to move;",
543 "Click by tool to move; right-click to wait;", "Scroll wheel to rotate hook;",
544 "Scroll wheel with right button held down to undo/redo."]
545 IMEdit
-> ["Left-click to draw selected; scroll to change selection;",
546 "Right-click on piece to select, drag to move;",
547 "While holding right-click: left-click to advance time, middle-click to delete;",
548 "Scroll wheel to rotate selected piece; scroll wheel while held down to undo/redo."]
549 IMReplay
-> ["Scroll wheel with right button held down to undo/redo."]
550 IMMeta
-> ["Left-clicking on something does most obvious thing;"
551 , "Right-clicking does second-most obvious thing."]]
554 "Basic game instructions:"
555 , "Choose [C]odename, then [R]egister it;"
556 , "select other players, and [S]olve their locks;"
557 , "go [H]ome, then [E]dit and [P]lace a lock of your own;"
558 , "you can then [D]eclare your solutions."
559 , "Make other players green by solving their locks and not letting them solve yours."]]
561 renderStrColAt cyan
"Keybindings:" $ (screenHeightHexes`
div`
4)*^
(hv
+^neg hw
)
562 let keybindingsHeight
= screenHeightHexes
- (3 + length extraHelpStrs
+ sum (map length extraHelpStrs
))
563 sequence_ [ with
$ renderStrColAtLeft messageCol
564 ( keysStr
++ ": " ++ desc
)
565 $ (x
*bdgWidth
-(screenWidthHexes
-6)`
div`
2)*^hu
+^ neg hv
+^
566 (screenHeightHexes`
div`
4 - y`
div`
2)*^
(hv
+^neg hw
) +^
568 |
((keysStr
,with
,desc
),(x
,y
)) <- zip [(keysStr
,with
,desc
)
569 |
group <- groupBy ((==) `on`
snd) $ sortBy (compare `on`
snd) bdgs
570 , let cmd
= snd $ head group
571 , let desc
= describeCommand cmd
573 , let chs
= map fst group
574 , let keysStr
= showKeys chs
575 , let with
= if True -- 3*(bdgWidth-1) < length desc + length keysStr + 1
576 then withFont smallFont
579 (map (`
divMod` keybindingsHeight
) [0..])
580 , (x
+1)*bdgWidth
< screenWidthHexes
]
581 sequence_ [ renderStrColAt
(if firstLine
then cyan
else messageCol
) str
582 $ (screenHeightHexes`
div`
4 - y`
div`
2)*^
(hv
+^neg hw
)
585 |
((str
,firstLine
),y
) <- (intercalate
[("",False)] $ (map (`
zip`
(True:repeat False)) extraHelpStrs
)) `
zip`
[(keybindingsHeight
+1)..] ]
588 showHelp IMMeta HelpPageGame
= do
591 let headPos
= (screenHeightHexes`
div`
4)*^
(hv
+^neg hw
)
592 renderStrColAt red
"INTRICACY" headPos
594 [ renderStrColAt purple str
$
596 +^
(y`
div`
2)*^
(hw
+^neg hv
)
598 |
(y
,str
) <- zip [1..]
602 showHelp _ _
= return False
604 onNewMode mode
= modify
(\ds
-> ds
{needHoverUpdate
=True}) >> say
""
606 drawShortMouseHelp mode
= do
607 mwhs
<- gets
$ whsButtons
.uiOptions
608 showBT
<- showButtonText
<$> gets uiOptions
609 when (showBT
&& isNothing mwhs
) $ do
610 let helps
= shortMouseHelp mode
611 smallFont
<- gets dispFontSmall
612 renderToMain
$ withFont smallFont
$ sequence_
613 [ renderStrColAtLeft
(dim cyan
) help
614 (periphery
3 +^ neg hu
+^
(2-n
)*^hv
)
615 |
(n
,help
) <- zip [0..] helps
]
617 shortMouseHelp IMPlay
=
618 [ "LMB: select/move tool"
619 , "LMB+drag: move tool"
622 , "RMB+Wheel: undo/redo"
624 shortMouseHelp IMEdit
=
625 [ "LMB: paint; Ctrl+LMB: delete"
626 , "Wheel: set paint type"
627 , "RMB: select piece; drag to move"
628 , "RMB+LMB: wait; RMB+MMB: delete piece"
629 , "MMB+Wheel: undo/redo"
631 shortMouseHelp IMReplay
=
632 [ "Wheel: advance/regress time" ]
633 shortMouseHelp _
= []
635 -- waitEvent' : copy of SDL.Events.waitEvent, with the timeout increased
636 -- drastically to reduce CPU load when idling.
637 waitEvent
' :: IO Event
639 where loop
= do pumpEvents
642 NoEvent
-> threadDelay
10000 >> loop
650 getEventsTimeout us
= do
651 es
<- maybeToList <$> timeout us waitEvent
'
655 updateHoverStr
:: InputMode
-> UIM
()
656 updateHoverStr mode
= do
657 p
@(mPos
,isCentral
) <- gets mousePos
658 showBT
<- showButtonText
<$> gets uiOptions
659 hstr
<- runMaybeT
$ msum
660 [ MaybeT
( cmdAtMousePos p mode Nothing
) >>= lift
. describeCommandAndKeys
661 , guard showBT
>> MaybeT
(helpAtMousePos p mode
)
662 , guard (showBT
&& mode
== IMEdit
) >> msum
663 [ return $ "set paint mode: " ++ describeCommand
(paintTileCmds
!!i
)
664 | i
<- take (length paintTiles
) [0..]
665 , mPos
== paintButtonStart
+^ i
*^hv
]
666 , guard (mPos
== uiOptPos uiOB1
&& mode `
elem` uiOptModes uiOB1
) >> describeUIOptionButton uiOB1
667 , guard (mPos
== uiOptPos uiOB2
&& mode `
elem` uiOptModes uiOB2
) >> describeUIOptionButton uiOB2
668 , guard (mPos
== uiOptPos uiOB3
&& mode `
elem` uiOptModes uiOB3
) >> describeUIOptionButton uiOB3
669 , guard (mPos
== uiOptPos uiOB4
&& mode `
elem` uiOptModes uiOB4
) >> describeUIOptionButton uiOB4
670 , guard (mPos
== uiOptPos uiOB5
&& mode `
elem` uiOptModes uiOB5
) >> describeUIOptionButton uiOB5
672 , guard (mPos
== uiOptPos uiOB6
&& mode `
elem` uiOptModes uiOB6
) >> describeUIOptionButton uiOB6
675 modify
$ \ds
-> ds
{ hoverStr
= hstr
}
677 describeCommandAndKeys
:: Command
-> UIM
String
678 describeCommandAndKeys cmd
= do
679 uibdgs
<- Map
.findWithDefault
[] mode `
liftM` gets uiKeyBindings
680 return $ describeCommand cmd
++ " ["
681 ++ concat (intersperse ","
682 (map showKeyFriendly
$ findBindings
(uibdgs
++ bindings mode
) cmd
))
686 fillArea
:: HexVec
-> [HexVec
] -> [HexVec
-> MainStateT UIM
()] -> MainStateT UIM
()
687 fillArea centre area draws
= do
688 offset
<- gets listOffset
690 listButton cmd
= \pos
-> lift
$ registerButton pos cmd
3 []
691 draws
' = if offset
> 0 && length draws
> na
692 then listButton CmdPrevPage
:
693 drop (max 0 $ min (length draws
- (na
-1)) (na
-1 + (na
-2)*(offset
-1))) draws
695 selDraws
= if length draws
' > na
696 then take (na
-1) draws
' ++ [listButton CmdNextPage
]
698 sequence_ $ map (uncurry ($)) $
699 zip selDraws
$ sortBy (compare `on` hexVec2SVec
37) $
700 take (length selDraws
) $ sortBy
701 (compare `on`
(hexLen
. (-^centre
)))
704 drawOldLock ls pos
= void
.runMaybeT
$ msum [ do
706 lift
.lift
$ drawMiniLock lock pos
707 , lift
.lift
.renderToMain
$
708 renderStrColAt messageCol
(show ls
) pos
712 drawName name pos
= nameCol name
>>= drawNameCol name pos
713 drawNullName name pos
= drawNameCol name pos
$ invisible white
714 drawNameCol name pos col
= do
715 lift
.renderToMain
$ do
716 drawAtRel
(playerGlyph col
) pos
717 renderStrColAt buttonTextCol name pos
718 drawRelScore name pos
= do
720 relScore
<- getRelScore name
721 flip (maybe (return ())) relScore
$ \score
->
723 renderToMain
$ renderStrColAt col
724 ((if score
> 0 then "+" else "") ++ show score
) pos
725 registerSelectable pos
0 SelRelScore
727 drawNote note pos
= case noteBehind note
of
728 Just al
-> drawActiveLock al pos
729 Nothing
-> drawPublicNote
(noteAuthor note
) pos
730 drawActiveLock al
@(ActiveLock name i
) pos
= do
731 accessed
<- accessedAL al
732 drawNameWithChar name
733 (if accessed
then accColour
else white
)
734 (lockIndexChar i
) pos
735 drawPublicNote name
=
736 drawNameWithChar name pubColour
'P
'
737 drawNameWithChar name charcol char pos
= do
739 drawNameWithCharAndCol name charcol char col pos
740 drawNameWithCharAndCol
:: String -> Pixel
-> Char -> Pixel
-> HexVec
-> MainStateT UIM
()
741 drawNameWithCharAndCol name charcol char col pos
= do
742 size
<- fi
.snd <$> lift getGeom
743 let up
= SVec
0 $ - (ysize size
- size`
div`
2)
744 let down
= SVec
0 $ ysize size
745 smallFont
<- lift
$ gets dispFontSmall
746 lift
.renderToMain
$ do
747 drawAtRel
(playerGlyph col
) pos
749 renderStrColAt buttonTextCol name pos
750 displaceRender down
$ withFont smallFont
$
751 renderStrColAt charcol
[char
] pos
753 pubColour
= colourWheel pubWheelAngle
-- ==purple
756 ourName
<- (authUser
<$>) <$> gets curAuth
757 relScore
<- getRelScore name
758 return $ dim
$ case relScore
of
759 Nothing
-> Pixel
$ if ourName
== Just name
then 0xc0c0c000 else 0x80808000
760 Just score
-> scoreColour score
761 scoreColour
:: Int -> Pixel
762 scoreColour score
= Pixel
$ case score
of
771 drawLockInfo
:: ActiveLock
-> Maybe LockInfo
-> MainStateT UIM
()
772 drawLockInfo al
@(ActiveLock name idx
) Nothing
= do
773 let centre
= hw
+^neg hv
+^
7*(idx
-1)*^hu
774 lift
$ drawEmptyMiniLock centre
775 drawNameWithCharAndCol name white
(lockIndexChar idx
) (invisible white
) centre
776 ourName
<- (authUser
<$>) <$> gets curAuth
777 lift
$ registerSelectable centre
3 $ SelLockUnset
(ourName
== Just name
) al
778 drawLockInfo al
@(ActiveLock name idx
) (Just lockinfo
) = do
779 let centre
= locksPos
+^
7*(idx
-1)*^hu
780 let accessedByPos
= centre
+^
3*^
(hv
+^ neg hw
)
781 let accessedPos
= centre
+^
2*^
(hw
+^ neg hv
)
782 let notesPos
= centre
+^
3*^
(hw
+^ neg hv
)
783 ourName
<- (authUser
<$>) <$> gets curAuth
786 lock
<- mgetLock
$ lockSpec lockinfo
788 drawMiniLock lock centre
789 registerSelectable centre
3 $ SelLock al
791 drawActiveLock al centre
792 lift
$ registerSelectable centre
3 $ SelLock al
795 size
<- snd <$> lift getGeom
797 renderToMain
$ displaceRender
(SVec size
0) $ renderStrColAt
(brightish white
) "UNLOCKED BY" $ accessedByPos
+^ hv
798 registerSelectable
(accessedByPos
+^ hv
) 0 SelPrivyHeader
799 registerSelectable
(accessedByPos
+^ hv
+^ hu
) 0 SelPrivyHeader
802 renderToMain
$ renderStrColAt pubColour
"All" accessedByPos
803 registerSelectable accessedByPos
1 SelPublicLock
804 else if null $ accessedBy lockinfo
805 then lift
.renderToMain
$ renderStrColAt dimWhiteCol
"No-one" accessedByPos
806 else fillArea accessedByPos
807 [ accessedByPos
+^ d | j
<- [0..2], i
<- [-2..3]
809 , let d
= j
*^hw
+^ i
*^hu
]
810 $ [ \pos
-> (lift
$ registerSelectable pos
0 (SelSolution note
)) >> drawNote note pos
811 | note
<- lockSolutions lockinfo
] ++
812 [ \pos
-> (lift
$ registerSelectable pos
0 (SelAccessed name
)) >> drawName name pos
813 | name
<- accessedBy lockinfo
\\ map noteAuthor
(lockSolutions lockinfo
) ]
815 undecls
<- gets undeclareds
816 case if isJust $ guard . (|| public lockinfo
) . (`
elem`
map noteAuthor
(lockSolutions lockinfo
)) =<< ourName
817 then if public lockinfo
818 then Just
(pubColour
,"Accessed!",AccessedPublic
)
819 else Just
(accColour
, "Solved!",AccessedSolved
)
820 else if any (\(Undeclared _ ls _
) -> ls
== lockSpec lockinfo
) undecls
821 then Just
(yellow
, "Undeclared",AccessedUndeclared
)
824 Just
(col
,str
,selstr
) -> lift
$ do
825 renderToMain
$ renderStrColAt col str accessedPos
826 registerSelectable accessedPos
1 (SelAccessedInfo selstr
)
828 read <- take 3 <$> getNotesReadOn lockinfo
829 unless (ourName
== Just name
) $ do
830 let readPos
= accessedPos
+^
(-3)*^hu
831 lift
.renderToMain
$ renderStrColAt
(if length read == 3 then accColour
else dimWhiteCol
)
833 when (length read == 3) $ lift
$ registerSelectable readPos
0 (SelAccessedInfo AccessedReadNotes
)
834 fillArea
(accessedPos
+^neg hu
) [ accessedPos
+^ i
*^hu | i
<- [-1..1] ]
835 $ take 3 $ [ \pos
-> (lift
$ registerSelectable pos
0 (SelReadNote note
)) >> drawNote note pos
836 | note
<- read ] ++ (repeat $ \pos
-> (lift
$ registerSelectable pos
0 SelReadNoteSlot
>>
837 renderToMain
(drawAtRel
(HollowGlyph
$ dim green
) pos
)))
840 renderToMain
$ displaceRender
(SVec size
0) $ renderStrColAt
(brightish white
) "SECURING" $ notesPos
+^ hv
841 registerSelectable
(notesPos
+^ hv
) 0 SelNotesHeader
842 registerSelectable
(notesPos
+^ hv
+^ hu
) 0 SelNotesHeader
843 if null $ notesSecured lockinfo
844 then lift
.renderToMain
$ renderStrColAt dimWhiteCol
"None" notesPos
845 else fillArea notesPos
846 [ notesPos
+^ d | j
<- [0..2], i
<- [-2..3]
848 , let d
= j
*^hw
+^ i
*^hu
]
849 [ \pos
-> (lift
$ registerSelectable pos
0 (SelSecured note
)) >> drawActiveLock
(noteOn note
) pos
850 | note
<- notesSecured lockinfo
]