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/.
12 {-# LANGUAGE FlexibleContexts #-}
13 {-# LANGUAGE FlexibleInstances #-}
14 module SDLUIMInstance
() where
16 import Control
.Applicative
17 import Control
.Concurrent
(threadDelay
)
18 import Control
.Concurrent
.STM
19 import Control
.Monad
.State
20 import Control
.Monad
.Trans
.Maybe
21 import Control
.Monad
.Trans
.Reader
23 import Data
.Foldable
(for_
)
24 import Data
.Function
(on
)
27 import qualified Data
.Map
as Map
29 import qualified Data
.Vector
as Vector
31 import Graphics
.UI
.SDL
hiding (flip, name
)
32 import qualified Graphics
.UI
.SDL
as SDL
33 import qualified Graphics
.UI
.SDL
.TTF
as TTF
34 import Safe
(maximumBound
)
36 --import Debug.Trace (traceShow)
56 instance UIMonad
(StateT UIState
IO) where
57 runUI m
= evalStateT m nullUIState
59 lift
$ clearButtons
>> clearSelectables
64 lift
. drawTitle
=<< getTitle
67 drawUIOptionButtons mode
70 drawShortMouseHelp mode s
73 drawPrompt full s
= say
$ s
++ (if full
then "" else "_")
77 reportAlerts
= playAlertSounds
79 getChRaw
= resetMouseButtons
>> getChRaw
'
81 resetMouseButtons
= modify
$ \s
-> s
82 { leftButtonDown
= Nothing
83 , middleButtonDown
= Nothing
84 , rightButtonDown
= Nothing
87 events
<- liftIO getEvents
88 if not.null $ [ True | MouseButtonDown _ _ ButtonRight
<- events
]
90 else maybe getChRaw
' (return.Just
) $ listToMaybe $ [ ch
91 | KeyDown
(Keysym _ _ ch
) <- events
94 setUIBinding mode cmd ch
=
95 modify
$ \s
-> s
{ uiKeyBindings
=
96 Map
.insertWith
(\ [bdg
] bdgs
-> if bdg `
elem` bdgs
then delete bdg bdgs
else bdg
:bdgs
)
97 mode
[(ch
,cmd
)] $ uiKeyBindings s
}
99 getUIBinding mode cmd
= ($cmd
) <$> getBindingStr mode
101 initUI
= (isJust <$>) . runMaybeT
$ do
102 catchIOErrorMT
$ SDL
.init
104 [InitVideo
,InitAudio
]
108 catchIOErrorMT TTF
.init
115 liftIO
$ warpMouse
(fi
$ w`
div`
2) (fi
$ h`
div`
2)
120 catchIOErrorMT m
= MaybeT
. liftIO
. ignoreIOErrAlt
$ m
>> return (Just
())
127 unblockInput
= return $ pushEvent VideoExpose
131 impatience ticks
= do
132 liftIO
$ threadDelay
50000
134 let pos
= serverWaitPos
135 smallFont
<- gets dispFontSmall
137 mapM_ (drawAtRel
(FilledHexGlyph
$ bright black
)) [ pos
+^ i
*^hu | i
<- [0..3] ]
139 renderStrColAtLeft errorCol
("waiting..."++replicate ((ticks`
div`
5)`
mod`
3) '.') pos
141 registerButton
(pos
+^ neg hv
) CmdQuit
0 [("abort",hu
+^neg hw
)]
142 drawButtons IMImpatience
144 cmds
<- getInput IMImpatience
145 return $ CmdQuit `
elem` cmds
149 (scrCentre
, size
) <- getGeom
150 centre
<- gets dispCentre
151 let SVec x y
= hexVec2SVec size
(pos
-^centre
) +^ scrCentre
152 liftIO
$ warpMouse
(fi x
) (fi y
)
153 lbp
<- gets leftButtonDown
154 rbp
<- gets rightButtonDown
155 let [lbp
',rbp
'] = ((const $ pos
-^ centre
) <$>) <$> [lbp
,rbp
]
156 modify
$ \s
-> s
{leftButtonDown
= lbp
', rightButtonDown
= rbp
'}
159 centre
<- gets dispCentre
160 gets
((Just
.(+^centre
).fst) . mousePos
)
164 registerButton
(periphery
5 +^ hw
+^ neg hv
) (CmdInputChar
'Y
') 2 [("confirm",hu
+^neg hw
)]
165 drawButtons IMTextInput
168 toggleColourMode
= modify
$ \s
-> s
{uiOptions
= (uiOptions s
){
169 useFiveColouring
= not $ useFiveColouring
$ uiOptions s
}}
173 events
<- liftIO
$ nubMouseMotions
<$> getEventsTimeout
(10^
6`
div`fps
)
174 (cmds
,uiChanged
) <- if null events
then return ([],False) else do
176 cmds
<- concat <$> mapM processEvent events
177 setPaintFromCmds cmds
179 return (cmds
,uistatesMayVisiblyDiffer oldUIState newUIState
)
180 now
<- liftIO getTicks
181 animFrameReady
<- gets
(maybe False (<now
) . nextAnimFrameAt
)
182 unless (null cmds
) clearMsg
183 return $ cmds
++ [CmdRefresh | uiChanged || animFrameReady
]
185 nubMouseMotions evs
=
186 -- drop all but last mouse motion event
187 let nubMouseMotions
' False (mm
@MouseMotion
{}:evs
) = mm
:nubMouseMotions
' True evs
188 nubMouseMotions
' True (mm
@MouseMotion
{}:evs
) = nubMouseMotions
' True evs
189 nubMouseMotions
' b
(ev
:evs
) = ev
:nubMouseMotions
' b evs
190 nubMouseMotions
' _
[] = []
191 in reverse $ nubMouseMotions
' False $ reverse evs
192 setPaintFromCmds cmds
= sequence_
193 [ modify
$ \s
-> s
{ paintTileIndex
= pti
}
194 |
(pti
,pt
) <- zip [0..] paintTiles
196 , (isNothing pt
&& cmd
== CmdDelete
) ||
199 CmdTile t
<- Just cmd
200 guard $ ((==)`on`tileType
) t pt
') ]
202 uistatesMayVisiblyDiffer uis1 uis2
=
203 uis1
{ mousePos
= (zero
,False), lastFrameTicks
=0 }
204 /= uis2
{mousePos
= (zero
,False), lastFrameTicks
=0 }
205 processEvent
(KeyDown
(Keysym _ _ ch
)) = case mode
of
206 IMTextInput
-> return [CmdInputChar ch
]
208 setting
<- gets settingBinding
209 if isJust setting
&& ch
/= '\0'
211 modify
$ \s
-> s
{settingBinding
= Nothing
}
212 when (ch
/= '\ESC
') $ setUIBinding mode
(fromJust setting
) ch
215 uibdgs
<- gets
(Map
.findWithDefault
[] mode
. uiKeyBindings
)
216 let mCmd
= lookup ch
$ uibdgs
++ bindings mode
217 return $ maybeToList mCmd
218 processEvent MouseMotion
{} = do
219 (oldMPos
,_
) <- gets mousePos
220 (pos
@(mPos
,_
),(sx
,sy
,sz
)) <- getMousePos
221 updateMousePos mode pos
222 lbp
<- gets leftButtonDown
223 rbp
<- gets rightButtonDown
224 centre
<- gets dispCentre
225 let drag
:: Maybe HexVec
-> Maybe Command
227 fromPos
@(HexVec x y z
) <- bp
228 -- check we've dragged at least a full hex's distance:
229 guard $ not.all (\(a
,b
) -> abs (fi a
- b
) < 1.0) $ [(x
,sx
),(y
,sy
),(z
,sz
)]
230 let dir
= hexVec2HexDirOrZero
$ mPos
-^ fromPos
232 return $ CmdDrag
(fromPos
+^centre
) dir
234 IMEdit
-> case drag rbp
of
235 Just cmd
-> return [cmd
]
236 Nothing
-> if mPos
/= oldMPos
238 pti
<- getEffPaintTileIndex
239 return $ CmdMoveTo
(mPos
+^ centre
) :
240 ([CmdPaintFromTo
(paintTiles
!!pti
) (oldMPos
+^centre
) (mPos
+^centre
) |
isJust lbp
])
242 IMPlay
-> return $ maybeToList $ msum $ map drag
[lbp
, rbp
]
245 mouseFromTo from to
= do
246 let dir
= hexVec2HexDirOrZero
$ to
-^ from
248 then (CmdDir WHSSelected dir
:) <$> mouseFromTo
(from
+^ dir
) to
250 processEvent
(MouseButtonDown _ _ ButtonLeft
) = do
251 pos
@(mPos
,central
) <- gets mousePos
252 modify
$ \s
-> s
{ leftButtonDown
= Just mPos
}
253 rb
<- gets
(isJust . rightButtonDown
)
254 mcmd
<- cmdAtMousePos pos mode
(Just
False)
255 let hotspotAction
= listToMaybe
256 $ map (\cmd
-> return [cmd
]) (maybeToList mcmd
)
257 ++ [ modify
(\s
-> s
{paintTileIndex
= i
}) >> return []
258 | i
<- take (length paintTiles
) [0..]
259 , mPos
== paintButtonStart
+^ i
*^hv
]
260 ++ [ toggleUIOption uiOB1
>> updateHoverStr mode
>> return []
261 | mPos
== uiOptPos uiOB1
&& mode `
elem` uiOptModes uiOB1
]
262 ++ [ toggleUIOption uiOB2
>> updateHoverStr mode
>> return []
263 | mPos
== uiOptPos uiOB2
&& mode `
elem` uiOptModes uiOB2
]
264 ++ [ toggleUIOption uiOB3
>> updateHoverStr mode
>> return []
265 | mPos
== uiOptPos uiOB3
&& mode `
elem` uiOptModes uiOB3
]
266 ++ [ toggleUIOption uiOB4
>> updateHoverStr mode
>> return []
267 | mPos
== uiOptPos uiOB4
&& mode `
elem` uiOptModes uiOB4
]
268 ++ [ toggleUIOption uiOB5
>> updateHoverStr mode
>> return []
269 | mPos
== uiOptPos uiOB5
&& mode `
elem` uiOptModes uiOB5
]
271 ++ [ toggleUIOption uiOB6
>> updateHoverStr mode
>> return []
272 | mPos
== uiOptPos uiOB6
&& mode `
elem` uiOptModes uiOB6
]
276 then return [ CmdWait
]
277 else flip fromMaybe hotspotAction
$ case mode
of
279 pti
<- getEffPaintTileIndex
280 return [ drawCmd
(paintTiles
!!pti
) False ]
282 centre
<- gets dispCentre
283 return [ CmdManipulateToolAt
$ mPos
+^ centre
]
285 processEvent
(MouseButtonUp _ _ ButtonLeft
) = do
286 modify
$ \s
-> s
{ leftButtonDown
= Nothing
}
288 processEvent
(MouseButtonDown _ _ ButtonRight
) = do
289 pos
@(mPos
,_
) <- gets mousePos
290 modify
$ \s
-> s
{ rightButtonDown
= Just mPos
}
291 lb
<- gets
(isJust . leftButtonDown
)
293 then return [ CmdWait
]
294 else (fromMaybe [] <$>) $ runMaybeT
$ msum
296 cmd
<- MaybeT
$ cmdAtMousePos pos mode Nothing
297 guard $ mode
/= IMTextInput
298 -- modify $ \s -> s { settingBinding = Just cmd }
299 return [ CmdBind
$ Just cmd
]
301 cmd
<- MaybeT
$ cmdAtMousePos pos mode
(Just
True)
304 IMPlay
-> return [ CmdClear
, CmdWait
]
305 _
-> return [ CmdClear
, CmdSelect
] ]
306 processEvent
(MouseButtonUp _ _ ButtonRight
) = do
307 modify
$ \s
-> s
{ rightButtonDown
= Nothing
}
308 return [ CmdUnselect | mode
== IMEdit
]
309 processEvent
(MouseButtonDown _ _ ButtonWheelUp
) = doWheel
1
310 processEvent
(MouseButtonDown _ _ ButtonWheelDown
) = doWheel
$ -1
311 processEvent
(MouseButtonDown _ _ ButtonMiddle
) = do
312 (mPos
,_
) <- gets mousePos
313 modify
$ \s
-> s
{ middleButtonDown
= Just mPos
}
314 rb
<- gets
(isJust . rightButtonDown
)
315 return $ [CmdDelete | rb
]
316 processEvent
(MouseButtonUp _ _ ButtonMiddle
) = do
317 modify
$ \s
-> s
{ middleButtonDown
= Nothing
}
319 processEvent
(VideoResize w h
) = do
322 processEvent VideoExpose
= return [ CmdRefresh
]
323 processEvent Quit
= return [ CmdForceQuit
]
325 processEvent _
= return []
328 rb
<- gets
(isJust . rightButtonDown
)
329 mb
<- gets
(isJust . middleButtonDown
)
330 if ((rb || mb || mode
== IMReplay
) && mode
/= IMEdit
)
331 ||
(mb
&& mode
== IMEdit
)
332 then return [ if dw
== 1 then CmdRedo
else CmdUndo
]
333 else if mode
/= IMEdit || rb
334 then return [ CmdRotate WHSSelected dw
]
336 modify
$ \s
-> s
{ paintTileIndex
= (paintTileIndex s
+ dw
) `
mod`
length paintTiles
}
340 drawCmd mt
True = CmdPaint mt
341 drawCmd
(Just t
) False = CmdTile t
342 drawCmd Nothing _
= CmdDelete
344 getMousePos
:: UIM
((HexVec
,Bool),(Double,Double,Double))
346 (scrCentre
, size
) <- getGeom
347 (x
,y
,_
) <- lift getMouseState
348 let sv
= SVec
(fi x
) (fi y
) +^ neg scrCentre
349 let mPos
@(HexVec x y z
) = sVec2HexVec size sv
350 let (sx
,sy
,sz
) = sVec2dHV size sv
351 let isCentral
= all (\(a
,b
) -> abs (fi a
- b
) < 0.5)
352 [(x
,sx
),(y
,sy
),(z
,sz
)]
353 return ((mPos
,isCentral
),(sx
,sy
,sz
))
354 updateMousePos mode newPos
= do
355 oldPos
<- gets mousePos
356 when (newPos
/= oldPos
) $ do
357 modify
$ \ds
-> ds
{ mousePos
= newPos
}
360 showHelp mode HelpPageInput
= do
361 bdgs
<- nub <$> getBindings mode
362 smallFont
<- gets dispFontSmall
365 let extraHelpStrs
= (["Mouse commands:", "Hover over a button to see keys, right-click to rebind;"]
367 IMPlay
-> ["Click on tool to select, drag to move;",
368 "Click by tool to move; right-click to wait;", "Scroll wheel to rotate hook;",
369 "Scroll wheel with right button held down to undo/redo."]
370 IMEdit
-> ["Left-click to draw selected; scroll to change selection;",
371 "Right-click on piece to select, drag to move;",
372 "While holding right-click: left-click to advance time, middle-click to delete;",
373 "Scroll wheel to rotate selected piece; scroll wheel while held down to undo/redo."]
374 IMReplay
-> ["Scroll wheel for undo/redo."]
375 IMMeta
-> ["Left-clicking on something does most obvious thing;"
376 , "Right-clicking does second-most obvious thing."])
379 "Basic game instructions:"
380 , "Choose [C]odename, then [R]egister it;"
381 , "select other players, and [S]olve their locks;"
382 , "go [H]ome, then [E]dit and [P]lace a lock of your own;"
383 , "you can then [D]eclare your solutions."
384 , "Make other players green by solving their locks and not letting them solve yours."]]
387 renderStrColAtCentre cyan
"Keybindings:" $ (screenHeightHexes`
div`
4)*^
(hv
+^neg hw
)
388 let keybindingsHeight
= screenHeightHexes
- (3 + length extraHelpStrs
+ sum (map length extraHelpStrs
))
389 bdgWidth
= (screenWidthHexes
-6) `
div`
3
390 showKeys chs
= intercalate
"/" (map showKeyFriendly chs
)
391 sequence_ [ with
$ renderStrColAtLeft messageCol
392 ( keysStr
++ ": " ++ desc
)
393 $ (x
*bdgWidth
-(screenWidthHexes
-6)`
div`
2)*^hu
+^ neg hv
+^
394 (screenHeightHexes`
div`
4 - y`
div`
2)*^
(hv
+^neg hw
) +^
396 |
((keysStr
,with
,desc
),(x
,y
)) <- zip [(keysStr
,with
,desc
)
397 |
group <- groupBy ((==) `on`
snd) $ sortBy (compare `on`
snd) bdgs
398 , let cmd
= snd $ head group
399 , let desc
= describeCommand cmd
401 , let chs
= map fst group
402 , let keysStr
= showKeys chs
403 , let with
= if True -- 3*(bdgWidth-1) < length desc + length keysStr + 1
404 then withFont smallFont
407 (map (`
divMod` keybindingsHeight
) [0..])
408 , (x
+1)*bdgWidth
< screenWidthHexes
]
409 sequence_ [ renderStrColAtCentre
(if firstLine
then cyan
else messageCol
) str
410 $ (screenHeightHexes`
div`
4 - y`
div`
2)*^
(hv
+^neg hw
)
413 |
((str
,firstLine
),y
) <- intercalate
[("",False)] (map (`
zip`
414 (True:repeat False)) extraHelpStrs
) `
zip`
415 --[(keybindingsHeight+1)..]
416 [((screenHeightHexes
- sum (length <$> extraHelpStrs
)) `
div`
2)..]
420 showHelp IMInit HelpPageGame
= do
421 renderToMain
$ drawBasicHelpPage
("INTRICACY",red
) (initiationHelpText
,purple
)
423 showHelp IMMeta HelpPageGame
= do
424 renderToMain
$ drawBasicHelpPage
("INTRICACY",red
) (metagameHelpText
,purple
)
426 showHelp IMMeta
(HelpPageInitiated n
) = do
427 renderToMain
$ drawBasicHelpPage
("Initiation complete",purple
) (initiationCompleteText n
,red
)
429 showHelp IMEdit HelpPageFirstEdit
= do
430 renderToMain
$ drawBasicHelpPage
("Your first lock:",purple
) (firstEditHelpText
,green
)
432 showHelp _ _
= return False
434 onNewMode mode
= clearMsg
438 modify
$ \uiState
-> uiState
{bgSurface
=Nothing
}
440 isNothing <$> gets bgSurface
>>?
441 modify
(\uiState
-> uiState
{bgSurface
=bg
})
443 drawMainState
' :: MainState
-> MainStateT UIM
()
444 drawMainState
' PlayState
{ psCurrentState
=st
, psLastAlerts
=alerts
,
445 wrenchSelected
=wsel
, psTutLevel
=tutLevel
, psSolved
=solved
} = do
446 canUndo
<- gets
(null . psGameStateMoveStack
)
447 canRedo
<- gets
(null . psUndoneStack
)
448 let isTut
= isJust tutLevel
450 let selTools
= [ idx |
451 (idx
, PlacedPiece pos p
) <- enumVec
$ placedPieces st
452 , (wsel
&& isWrench p
) ||
(not wsel
&& isHook p
) ]
453 drawMainGameState selTools
False alerts st
454 lb
<- gets
(isJust . leftButtonDown
)
455 rb
<- gets
(isJust . leftButtonDown
)
457 centre
<- gets dispCentre
459 [ registerSelectable
(pos
-^ centre
) 0 $
460 if isWrench p
then SelToolWrench
else SelToolHook
462 , PlacedPiece pos p
<- Vector
.toList
$ placedPieces st
464 unless (noUndoTutLevel tutLevel
) $ do
465 registerUndoButtons canUndo canRedo
466 registerButtonGroup markButtonGroup
467 registerButton
(periphery
0) CmdOpen
(if solved
then 2 else 0) $
468 ("open", hu
+^neg hw
) : [("Press-->",9*^neg hu
) | solved
&& isTut
]
469 drawMainState
' ReplayState
{ rsCurrentState
=st
, rsLastAlerts
=alerts
} = do
470 canUndo
<- gets
(null . rsGameStateMoveStack
)
471 canRedo
<- gets
(null . rsMoveStack
)
473 drawMainGameState
[] False alerts st
474 registerUndoButtons canUndo canRedo
475 renderToMain
$ drawCursorAt Nothing
476 drawMainState
' EditState
{ esGameState
=st
, esGameStateStack
=sts
, esUndoneStack
=undostack
,
477 selectedPiece
=selPiece
, selectedPos
=selPos
} = lift
$ do
478 drawMainGameState
(maybeToList selPiece
) True [] st
479 renderToMain
$ drawCursorAt
$ if isNothing selPiece
then Just selPos
else Nothing
480 registerUndoButtons
(null sts
) (null undostack
)
481 when (isJust selPiece
) $ mapM_ registerButtonGroup
482 [ singleButton
(periphery
2 +^
3*^hw
+^hv
) CmdDelete
0 [("delete",hu
+^neg hw
)]
483 , singleButton
(periphery
2 +^
3*^hw
) CmdMerge
1 [("merge",hu
+^neg hw
)]
486 [ unless (any (pred . placedPiece
) . Vector
.toList
$ placedPieces st
)
487 $ registerButton
(periphery
0 +^ d
) cmd
2 [("place",hu
+^neg hw
),(tool
,hu
+^neg hv
)]
488 |
(pred,tool
,cmd
,d
) <- [
489 (isWrench
, "wrench", CmdTile
$ WrenchTile zero
, (-4)*^hv
+^ hw
),
490 (isHook
, "hook", CmdTile HookTile
, (-3)*^hv
+^ hw
) ] ]
492 drawMainState
' InitState
{initLocks
=initLocks
, tutProgress
=TutProgress
{tutSolved
=tutSolved
}} = lift
$ do
493 renderToMain
(erase
>> drawCursorAt Nothing
)
494 renderToMain
. renderStrColAtCentre white
"I N T R I C A C Y" $ 3 *^
(hv
+^ neg hw
)
496 mapM_ drawInitLock
$ Map
.keys accessible
497 registerButton
(tutPos
+^
3 *^ neg hu
+^ hv
) (CmdSolveInit Nothing
) 2
498 [("solve",hu
+^neg hw
),("lock",hu
+^neg hv
)]
500 accessible
= accessibleInitLocks tutSolved initLocks
501 tutPos
= (maximumBound
0 $ hx
<$> Map
.keys accessible
) *^ neg hu
502 name v | v
== zero
= "TUT"
503 |
otherwise = maybe "???" initLockName
$ Map
.lookup v accessible
504 solved v | v
== zero
= tutSolved
505 |
otherwise = Just
True == (initLockSolved
<$> Map
.lookup v accessible
)
507 let pos
= tutPos
+^
2 *^ v
508 drawNameCol
(name v
) pos
$ if solved v
then brightish purple
else brightish green
509 renderToMain
$ sequence_
510 [ (if open
then PathGlyph h
$ brightish white
511 else GateGlyph h
$ (if inbounds
then dim
else bright
) white
)
512 `drawAtRel`
(pos
+^ h
)
515 , let inbounds
= abs (hy v
') < 2 && hx v
' >= 0 && hz v
' <= 0
516 , not inbounds || h `
elem`
[hu
, neg hw
, neg hv
]
517 , let open
= inbounds
&& (solved v || solved v
') ]
518 registerSelectable pos
0 $ if v
== zero
then SelTut
else SelInitLock v
519 drawMainState
' MetaState
{curServer
=saddr
, undeclareds
=undecls
,
520 cacheOnly
=cOnly
, curAuth
=auth
, codenameStack
=names
,
521 randomCodenames
=rnamestvar
, retiredLocks
=mretired
, curLockPath
=path
,
522 curLock
=mlock
, listOffset
=offset
, asyncCount
=count
} = do
523 let ourName
= authUser
<$> auth
524 let selName
= listToMaybe names
525 let home
= isJust ourName
&& ourName
== selName
526 lift
$ renderToMain
(erase
>> drawCursorAt Nothing
)
528 smallFont
<- gets dispFontSmall
529 renderToMain
$ withFont smallFont
$ renderStrColAtLeft purple
530 (saddrStr saddr
++ if cOnly
then " (offline mode)" else "")
533 when (length names
> 1) $ lift
$ registerButton
534 (codenamePos
+^ neg hu
+^
2*^hw
) CmdBackCodename
0 [("back",3*^hw
)]
537 name
<- MaybeT
(return selName
)
538 FetchedRecord fresh err muirc
<- lift
$ getUInfoFetched
300 name
539 pending
<- ((>0) <$>) $ liftIO
$ readTVarIO count
542 unless ((fresh
&& not pending
) || cOnly
) $ do
543 smallFont
<- gets dispFontSmall
544 let str
= if pending
then "(response pending)" else "(updating)"
545 renderToMain
$ withFont smallFont
$
546 renderStrColBelow
(opaquify
$ dim errorCol
) str codenamePos
547 maybe (return ()) (setMsgLineNoRefresh errorCol
) err
548 when (fresh
&& (isNothing ourName ||
isNothing muirc || home
)) $
549 let reg
= isNothing muirc ||
isJust ourName
550 in registerButton
(codenamePos
+^
2*^hu
)
551 (if reg
then CmdRegister
$ isJust ourName
else CmdAuth
)
552 (if isNothing ourName
then 2 else 0)
553 [(if reg
then "reg" else "auth", 3*^hw
)]
554 (if isJust muirc
then drawName
else drawNullName
) name codenamePos
555 lift
$ registerSelectable codenamePos
0 (SelSelectedCodeName name
)
556 drawRelScore name
(codenamePos
+^hu
)
557 when (isJust muirc
) $ lift
$
558 registerButton retiredPos CmdShowRetired
5 [("retired",hu
+^neg hw
)]
559 for_ muirc
$ \(RCUserInfo
(_
,uinfo
)) -> case mretired
of
562 (map (locksPos
+^
) $ zero
:[rotate n
$ 4*^hu
-^
4*^hw | n
<- [0,2,3,5]])
563 [ \pos
-> lift
(registerSelectable pos
1 (SelOldLock ls
)) >> drawOldLock ls pos
565 lift
$ registerButton
(retiredPos
+^ hv
) (CmdPlayLockSpec Nothing
) 1 [("play",hu
+^neg hw
),("no.",hu
+^neg hv
)]
567 sequence_ [ drawLockInfo
(ActiveLock
(codename uinfo
) i
) mlockinfo |
568 (i
,mlockinfo
) <- assocs $ userLocks uinfo
]
569 when (isJust $ msum $ elems $ userLocks uinfo
) $ lift
$ do
570 registerButton interactButtonsPos
(CmdSolve Nothing
) 2 [("solve",hu
+^neg hw
),("lock",hu
+^neg hv
)]
571 when (isJust ourName
) $
572 registerButton
(interactButtonsPos
+^hw
) (CmdViewSolution Nothing
) 1 [("view",hu
+^neg hw
),("soln",hu
+^neg hv
)]
575 lift
.renderToMain
$ renderStrColAt messageCol
576 "Home" (codenamePos
+^hw
+^neg hv
)
577 unless (null undecls
) $ do
578 lift
.renderToMain
$ renderStrColAtLeft messageCol
"Undeclared:" (undeclsPos
+^
2*^hv
+^neg hu
)
579 lift
$ registerButton
(undeclsPos
+^hw
+^neg hu
) (CmdDeclare Nothing
) 2 [("decl",hv
+^
4*^neg hu
),("soln",hw
+^
4*^neg hu
)]
580 fillArea
(undeclsPos
+^hv
)
581 (map (undeclsPos
+^
) $ hexDisc
1 ++ [hu
+^neg hw
, neg hu
+^hv
])
582 [ \pos
-> lift
(registerSelectable pos
0 (SelUndeclared undecl
)) >> drawActiveLock al pos
583 | undecl
@(Undeclared _ _ al
) <- undecls
]
585 maybe (drawEmptyMiniLock miniLockPos
)
586 (`drawMiniLock` miniLockPos
)
588 registerSelectable miniLockPos
1 SelOurLock
589 registerButton
(miniLockPos
+^
3*^neg hw
+^
2*^hu
) CmdEdit
2
590 [("edit",hu
+^neg hw
),("lock",hu
+^neg hv
)]
591 registerButton lockLinePos CmdSelectLock
1 []
592 lift
$ unless (null path
) $ do
593 renderToMain
$ renderStrColAtLeft messageCol
(take 16 path
) $ lockLinePos
+^ hu
594 registerSelectable
(lockLinePos
+^
2*^hu
) 1 SelLockPath
596 [ registerButton
(miniLockPos
+^
2*^neg hv
+^
2*^hu
+^ dv
) cmd
1
597 [(dirText
,hu
+^neg hw
),("lock",hu
+^neg hv
)]
598 |
(dv
,cmd
,dirText
) <- [(zero
,CmdPrevLock
,"prev"),(neg hw
,CmdNextLock
,"next")] ]
599 let tested
= maybe False (isJust.snd) mlock
600 when (isJust mlock
&& home
) $ lift
$ registerButton
601 (miniLockPos
+^
2*^neg hw
+^
3*^hu
) (CmdPlaceLock Nothing
)
602 (if tested
then 2 else 1)
603 [("place",hu
+^neg hw
),("lock",hu
+^neg hv
)]
604 rnames
<- liftIO
$ readTVarIO rnamestvar
605 unless (null rnames
) $
606 fillArea randomNamesPos
607 (map (randomNamesPos
+^
) $ hexDisc
2)
608 [ \pos
-> lift
(registerSelectable pos
0 (SelRandom name
)) >> drawName name pos
611 when (ourName
/= selName
) $ void
$ runMaybeT
$ do
612 when (isJust ourName
) $
613 lift
.lift
$ registerButton
(codenamePos
+^ hw
+^ neg hv
) CmdHome
1 [("home",3*^hw
)]
614 sel
<- liftMaybe selName
615 us
<- liftMaybe ourName
616 ourUInfo
<- mgetUInfo us
617 selUInfo
<- mgetUInfo sel
618 let accesses
= map (uncurry getAccessInfo
) [(ourUInfo
,selUInfo
),(selUInfo
,ourUInfo
)]
619 let posLeft
= scoresPos
+^ hw
+^ neg hu
620 let posRight
= posLeft
+^
3*^hu
621 size
<- snd <$> (lift
.lift
) getGeom
623 lift
.renderToMain
$ renderStrColAbove
(brightish white
) "ESTEEM" scoresPos
624 lift
$ sequence_ [ registerSelectable
(scoresPos
+^v
) 0 SelRelScore | v
<- [hv
, hv
+^hu
] ]
625 drawRelScore sel scoresPos
626 fillArea
(posLeft
+^hw
) (map (posLeft
+^
) [zero
,hw
,neg hv
])
627 [ \pos
-> lift
(registerSelectable pos
0 (SelScoreLock
(Just sel
) accessed
$ ActiveLock us i
)) >>
628 drawNameWithCharAndCol us white
(lockIndexChar i
) col pos
630 , let accessed
= head accesses
!! i
632 | accessed
== Just AccessedPub
= dim pubColour
633 |
maybe False winsPoint accessed
= dim
$ scoreColour
$ -3
634 |
otherwise = obscure
$ scoreColour
3 ]
635 fillArea
(posRight
+^hw
) (map (posRight
+^
) [zero
,hw
,neg hv
])
636 [ \pos
-> lift
(registerSelectable pos
0 (SelScoreLock Nothing accessed
$ ActiveLock sel i
)) >>
637 drawNameWithCharAndCol sel white
(lockIndexChar i
) col pos
639 , let accessed
= accesses
!! 1 !! i
641 | accessed
== Just AccessedPub
= obscure pubColour
642 |
maybe False winsPoint accessed
= dim
$ scoreColour
3
643 |
otherwise = obscure
$ scoreColour
$ -3 ]
644 (posScore
,negScore
) <- MaybeT
$ (snd<$>) <$> getRelScoreDetails sel
645 lift
.lift
$ sequence_
647 renderToMain
$ renderStrColAt
(scoreColour score
) (sign
:show (abs score
)) pos
648 registerSelectable pos
0 SelRelScoreComponent
649 |
(sign
,score
,pos
) <-
650 [ ('-',-negScore
,posLeft
+^neg hv
+^hw
)
651 , ('+',posScore
,posRight
+^neg hv
+^hw
) ] ]
654 drawShortMouseHelp mode s
= do
655 mwhs
<- gets
$ whsButtons
.uiOptions
656 showBT
<- gets
(showButtonText
. uiOptions
)
657 when (showBT
&& isNothing mwhs
) $ do
658 let helps
= shortMouseHelp mode s
659 smallFont
<- gets dispFontSmall
660 renderToMain
$ withFont smallFont
$ sequence_
661 [ renderStrColAtLeft
(dim white
) help
662 (periphery
3 +^ neg hu
+^
(2-n
)*^hv
)
663 |
(n
,help
) <- zip [0..] helps
]
665 shortMouseHelp IMPlay PlayState
{ psTutLevel
= tutLevel
} =
666 [ "LMB: select/move tool"
667 , "LMB+drag: move tool" ] ++
669 |
not $ wrenchOnlyTutLevel tutLevel
] ++
670 [ "RMB+Wheel: undo/redo"
671 |
not $ noUndoTutLevel tutLevel
] ++
673 |
isNothing tutLevel
]
674 shortMouseHelp IMEdit _
=
675 [ "LMB: paint; Ctrl+LMB: delete"
676 , "Wheel: set paint type"
677 , "RMB: select piece; drag to move"
678 , "RMB+LMB: wait; RMB+MMB: delete piece"
679 , "MMB+Wheel: undo/redo"
681 shortMouseHelp IMReplay _
=
682 [ "Wheel: advance/regress time" ]
683 shortMouseHelp _ _
= []
685 -- waitEvent' : copy of SDL.Events.waitEvent, with the timeout increased
686 -- drastically to reduce CPU load when idling.
687 waitEvent
' :: IO Event
689 where loop
= do pumpEvents
692 NoEvent
-> threadDelay
10000 >> loop
700 getEventsTimeout us
= do
701 es
<- maybeToList <$> timeout us waitEvent
'
705 updateHoverStr
:: InputMode
-> UIM
()
706 updateHoverStr mode
= do
707 p
@(mPos
,isCentral
) <- gets mousePos
708 showBT
<- gets
(showButtonText
. uiOptions
)
709 hstr
<- runMaybeT
$ msum
710 [ MaybeT
( cmdAtMousePos p mode Nothing
) >>= lift
. describeCommandAndKeys
711 , guard showBT
>> MaybeT
(helpAtMousePos p mode
)
712 , guard (showBT
&& mode
== IMEdit
) >> msum
713 [ return $ "set paint mode: " ++ describeCommand
(paintTileCmds
!!i
)
714 | i
<- take (length paintTiles
) [0..]
715 , mPos
== paintButtonStart
+^ i
*^hv
]
716 , guard (mPos
== uiOptPos uiOB1
&& mode `
elem` uiOptModes uiOB1
) >> describeUIOptionButton uiOB1
717 , guard (mPos
== uiOptPos uiOB2
&& mode `
elem` uiOptModes uiOB2
) >> describeUIOptionButton uiOB2
718 , guard (mPos
== uiOptPos uiOB3
&& mode `
elem` uiOptModes uiOB3
) >> describeUIOptionButton uiOB3
719 , guard (mPos
== uiOptPos uiOB4
&& mode `
elem` uiOptModes uiOB4
) >> describeUIOptionButton uiOB4
720 , guard (mPos
== uiOptPos uiOB5
&& mode `
elem` uiOptModes uiOB5
) >> describeUIOptionButton uiOB5
722 , guard (mPos
== uiOptPos uiOB6
&& mode `
elem` uiOptModes uiOB6
) >> describeUIOptionButton uiOB6
725 modify
$ \ds
-> ds
{ hoverStr
= hstr
}
727 describeCommandAndKeys
:: Command
-> UIM
String
728 describeCommandAndKeys cmd
= do
729 uibdgs
<- gets
(Map
.findWithDefault
[] mode
. uiKeyBindings
)
730 return $ describeCommand cmd
++ " ["
732 (map showKeyFriendly
$ findBindings
(uibdgs
++ bindings mode
) cmd
)
736 fillArea
:: HexVec
-> [HexVec
] -> [HexVec
-> MainStateT UIM
()] -> MainStateT UIM
()
737 fillArea centre area draws
= do
738 offset
<- gets listOffset
740 listButton cmd
= \pos
-> lift
$ registerButton pos cmd
3 []
741 draws
' = if offset
> 0 && length draws
> na
742 then listButton CmdPrevPage
:
743 drop (max 0 $ min (length draws
- (na
-1)) (na
-1 + (na
-2)*(offset
-1))) draws
745 selDraws
= if length draws
' > na
746 then take (na
-1) draws
' ++ [listButton CmdNextPage
]
748 mapM_ (uncurry ($)) (
749 zip selDraws
$ sortBy (compare `on` hexVec2SVec
37) $
750 take (length selDraws
) $ sortBy
751 (compare `on`
(hexLen
. (-^centre
)))
754 drawOldLock ls pos
= void
.runMaybeT
$ msum [ do
756 lift
.lift
$ drawMiniLock lock pos
757 , lift
.lift
.renderToMain
$
758 renderStrColAt messageCol
(show ls
) pos
762 drawName
,drawNullName
:: Codename
-> HexVec
-> MainStateT UIM
()
763 drawName name pos
= nameCol name
>>= lift
. drawNameCol name pos
764 drawNullName name pos
= lift
. drawNameCol name pos
$ invisible white
766 drawNameCol name pos col
= renderToMain
$ do
767 drawAtRel
(playerGlyph col
) pos
768 renderStrColAt buttonTextCol name pos
770 drawRelScore name pos
= do
772 relScore
<- getRelScore name
773 flip (maybe (return ())) relScore
$ \score
->
775 renderToMain
$ renderStrColAt col
776 ((if score
> 0 then "+" else "") ++ show score
) pos
777 registerSelectable pos
0 SelRelScore
779 drawNote note pos
= case noteBehind note
of
780 Just al
-> drawActiveLock al pos
781 Nothing
-> drawPublicNote
(noteAuthor note
) pos
782 drawActiveLock al
@(ActiveLock name i
) pos
= do
783 accessed
<- accessedAL al
784 drawNameWithChar name
785 (if accessed
then accColour
else white
)
786 (lockIndexChar i
) pos
787 drawPublicNote name
=
788 drawNameWithChar name pubColour
'P
'
789 drawNameWithChar name charcol char pos
= do
791 drawNameWithCharAndCol name charcol char col pos
792 drawNameWithCharAndCol
:: String -> Pixel
-> Char -> Pixel
-> HexVec
-> MainStateT UIM
()
793 drawNameWithCharAndCol name charcol char col pos
= do
794 size
<- fi
.snd <$> lift getGeom
795 let up
= FVec
0 $ 1/2 - ylen
796 let down
= FVec
0 ylen
797 smallFont
<- lift
$ gets dispFontSmall
798 lift
.renderToMain
$ do
799 drawAtRel
(playerGlyph col
) pos
801 renderStrColAt buttonTextCol name pos
802 displaceRender down
$ withFont smallFont
$
803 renderStrColAt charcol
[char
] pos
805 pubColour
= colourWheel pubWheelAngle
-- ==purple
808 ourName
<- gets
((authUser
<$>) . curAuth
)
809 relScore
<- getRelScore name
810 return $ dim
$ case relScore
of
811 Nothing
-> Pixel
$ if ourName
== Just name
then 0xc0c0c000 else 0x80808000
812 Just score
-> scoreColour score
813 scoreColour
:: Int -> Pixel
814 scoreColour score
= Pixel
$ case score
of
823 drawLockInfo
:: ActiveLock
-> Maybe LockInfo
-> MainStateT UIM
()
824 drawLockInfo al
@(ActiveLock name idx
) Nothing
= do
825 let centre
= hw
+^neg hv
+^
7*(idx
-1)*^hu
826 lift
$ drawEmptyMiniLock centre
827 drawNameWithCharAndCol name white
(lockIndexChar idx
) (invisible white
) centre
828 ourName
<- gets
((authUser
<$>) . curAuth
)
829 lift
$ registerSelectable centre
3 $ SelLockUnset
(ourName
== Just name
) al
830 drawLockInfo al
@(ActiveLock name idx
) (Just lockinfo
) = do
831 let centre
= locksPos
+^
7*(idx
-1)*^hu
832 let accessedByPos
= centre
+^
3*^
(hv
+^ neg hw
)
833 let accessedPos
= centre
+^
2*^
(hw
+^ neg hv
)
834 let notesPos
= centre
+^
3*^
(hw
+^ neg hv
)
835 ourName
<- gets
((authUser
<$>) . curAuth
)
838 lock
<- mgetLock
$ lockSpec lockinfo
840 drawMiniLock lock centre
841 registerSelectable centre
3 $ SelLock al
843 drawActiveLock al centre
844 lift
$ registerSelectable centre
3 $ SelLock al
847 size
<- snd <$> lift getGeom
849 renderToMain
$ displaceRender
(FVec
1 0) $ renderStrColAt
(brightish white
) "UNLOCKED BY" $ accessedByPos
+^ hv
850 registerSelectable
(accessedByPos
+^ hv
) 0 SelPrivyHeader
851 registerSelectable
(accessedByPos
+^ hv
+^ hu
) 0 SelPrivyHeader
854 renderToMain
$ renderStrColAt pubColour
"All" accessedByPos
855 registerSelectable accessedByPos
1 SelPublicLock
856 else if null $ accessedBy lockinfo
857 then lift
.renderToMain
$ renderStrColAt dimWhiteCol
"No-one" accessedByPos
858 else fillArea accessedByPos
859 [ accessedByPos
+^ d | j
<- [0..2], i
<- [-2..3]
861 , let d
= j
*^hw
+^ i
*^hu
]
862 $ [ \pos
-> lift
(registerSelectable pos
0 (SelSolution note
)) >> drawNote note pos
863 | note
<- lockSolutions lockinfo
] ++
864 [ \pos
-> lift
(registerSelectable pos
0 (SelAccessed name
)) >> drawName name pos
865 | name
<- accessedBy lockinfo
\\ map noteAuthor
(lockSolutions lockinfo
) ]
867 undecls
<- gets undeclareds
868 case if isJust $ guard . (|| public lockinfo
) . (`
elem`
map noteAuthor
(lockSolutions lockinfo
)) =<< ourName
869 then if public lockinfo
870 then Just
(pubColour
,"Accessed!",AccessedPublic
)
871 else Just
(accColour
, "Solved!",AccessedSolved
)
872 else if any (\(Undeclared _ ls _
) -> ls
== lockSpec lockinfo
) undecls
873 then Just
(yellow
, "Undeclared",AccessedUndeclared
)
876 Just
(col
,str
,selstr
) -> lift
$ do
877 renderToMain
$ renderStrColAt col str accessedPos
878 registerSelectable accessedPos
1 (SelAccessedInfo selstr
)
880 read <- take 3 <$> getNotesReadOn lockinfo
881 unless (ourName
== Just name
) $ do
882 let readPos
= accessedPos
+^
(-3)*^hu
883 lift
.renderToMain
$ renderStrColAt
(if length read == 3 then accColour
else dimWhiteCol
)
885 when (length read == 3) $ lift
$ registerSelectable readPos
0 (SelAccessedInfo AccessedReadNotes
)
886 fillArea
(accessedPos
+^neg hu
) [ accessedPos
+^ i
*^hu | i
<- [-1..1] ]
887 $ take 3 $ [ \pos
-> lift
(registerSelectable pos
0 (SelReadNote note
)) >> drawNote note pos
888 | note
<- read ] ++ repeat (\pos
-> lift
$ registerSelectable pos
0 SelReadNoteSlot
>>
889 renderToMain
(drawAtRel
(HollowGlyph
$ dim green
) pos
))
892 renderToMain
$ displaceRender
(FVec
1 0) $ renderStrColAt
(brightish white
) "SECURING" $ notesPos
+^ hv
893 registerSelectable
(notesPos
+^ hv
) 0 SelNotesHeader
894 registerSelectable
(notesPos
+^ hv
+^ hu
) 0 SelNotesHeader
895 if null $ notesSecured lockinfo
896 then lift
.renderToMain
$ renderStrColAt dimWhiteCol
"None" notesPos
897 else fillArea notesPos
898 [ notesPos
+^ d | j
<- [0..2], i
<- [-2..3]
900 , let d
= j
*^hw
+^ i
*^hu
]
901 [ \pos
-> lift
(registerSelectable pos
0 (SelSecured note
)) >> drawActiveLock
(noteOn note
) pos
902 | note
<- notesSecured lockinfo
]
904 drawBasicHelpPage
:: (String,Pixel
) -> ([String],Pixel
) -> RenderM
()
905 drawBasicHelpPage
(title
,titleCol
) (body
,bodyCol
) = do
907 let startPos
= hv
+^
(length body `
div`
4)*^
(hv
+^neg hw
)
908 renderStrColAtCentre titleCol title
$ startPos
+^ hv
+^neg hw
910 [ renderStrColAtCentre bodyCol str
$
912 +^
(y`
div`
2)*^
(hw
+^neg hv
)
914 |
(y
,str
) <- zip [0..] body
]