-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathMain.hs
268 lines (227 loc) · 10.2 KB
/
Main.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
module Main where
import Control.Concurrent (threadDelay)
import Control.Monad (forM_, guard, void)
import Control.Monad.Reader (MonadReader (..), runReaderT)
import Reflex
import Reflex.SDL2
--------------------------------------------------------------------------------
-- | An axis aligned bounding box.
data AABB = AABB InputMotion (V2 Int)
--------------------------------------------------------------------------------
-- | Convert a mouse button to an AABB.
mouseButtonToAABB :: MouseButtonEventData -> AABB
mouseButtonToAABB dat = AABB (mouseButtonEventMotion dat) pos
where P pos32 = mouseButtonEventPos dat
pos = fromIntegral <$> pos32
--------------------------------------------------------------------------------
-- | Convert a mouse button motion to color.
motionToColor :: InputMotion -> V4 Int
motionToColor Released = V4 255 0 0 128
motionToColor Pressed = V4 0 0 255 128
--------------------------------------------------------------------------------
-- | Renders an AABB using the handy SDL 2d 'Renderer'.
renderAABB :: MonadIO m => Renderer -> V4 Int -> V2 Int -> m ()
renderAABB r color pos = do
rendererDrawColor r $= (fromIntegral <$> color)
fillRect r $ Just $ Rectangle (P $ fromIntegral <$> pos - 10) 20
-------------------------------------------------------------------------------
-- | A type representing one layer in our app.
type Layer m = Performable m ()
----------------------------------------------------------------------
-- | Commit a layer stack that changes over time.
commitLayers :: (ReflexSDL2 t m, DynamicWriter t [Layer m] m)
=> Dynamic t [Layer m] -> m ()
commitLayers = tellDyn
----------------------------------------------------------------------
-- | Commit one layer that changes over time.
commitLayer :: (ReflexSDL2 t m, DynamicWriter t [Layer m] m)
=> Dynamic t (Layer m) -> m ()
commitLayer = tellDyn . fmap pure
ffor2 :: Reflex t => Dynamic t a -> Dynamic t b -> (a -> b -> c) -> Dynamic t c
ffor2 a b f = zipDynWith f a b
ffor2up
:: Reflex t => Dynamic t a -> Dynamic t b1 -> ((a, b1) -> b) -> Dynamic t b
ffor2up a b = ffor (zipDyn a b)
data ButtonState = ButtonStateUp
| ButtonStateOver
| ButtonStateDown
deriving Eq
buttonState :: Bool -> Bool -> ButtonState
buttonState isInside isDown
| not isInside = ButtonStateUp
| isDown = ButtonStateDown
| otherwise = ButtonStateOver
button
:: (ReflexSDL2 t m, DynamicWriter t [Layer m] m, MonadReader Renderer m)
=> m (Event t ButtonState)
button = do
evMotionData <- getMouseMotionEvent
let position = V2 100 100
size = V2 100 100
V2 tlx tly = position
V2 brx bry = position + size
evMotionPos = fmap fromIntegral . mouseMotionEventPos <$> evMotionData
evMouseIsInside = ffor evMotionPos $ \(P (V2 x y)) ->
(x >= tlx && x <= brx) && (y >= tly && y <= bry)
dMouseIsInside <- holdDyn False evMouseIsInside
evBtn <- getMouseButtonEvent
let evBtnIsDown = ffor evBtn $ (== Pressed) . mouseButtonEventMotion
dButtonIsDown <- holdDyn False evBtnIsDown
let dButtonStatePre = buttonState <$> dMouseIsInside <*> dButtonIsDown
evPB <- getPostBuild
dButtonState <- holdDyn ButtonStateUp $ leftmost [ updated dButtonStatePre
, ButtonStateUp <$ evPB
]
r <- ask
commitLayer $ ffor dButtonState $ \st -> do
let color = case st of
ButtonStateUp -> V4 192 192 192 255
ButtonStateOver -> 255
ButtonStateDown -> V4 128 128 128 255
rendererDrawColor r $= color
fillRect r $ Just $ Rectangle (P position) size
updated <$> holdUniqDyn dButtonState
guest
:: (ReflexSDL2 t m, DynamicWriter t [Layer m] m, MonadReader Renderer m)
=> m ()
guest = do
-- Print some stuff after the network is built.
evPB <- getPostBuild
performEvent_ $ ffor evPB $ \() ->
liftIO $ putStrLn "starting up..."
------------------------------------------------------------------------------
-- Get a handle on our renderer
------------------------------------------------------------------------------
r <- ask
------------------------------------------------------------------------------
-- Test async events.
-- This will wait three seconds before coloring the background black.
------------------------------------------------------------------------------
evDelay <- getAsyncEvent $ threadDelay 3000000
dDelay <- holdDyn False $ True <$ evDelay
commitLayers $ ffor dDelay $ \case
False -> pure $ do
rendererDrawColor r $= V4 128 128 128 255
fillRect r Nothing
True -> pure $ do
rendererDrawColor r $= V4 0 0 0 255
fillRect r Nothing
------------------------------------------------------------------------------
-- A button!
------------------------------------------------------------------------------
evBtnState <- button
let evBtnPressed = fmapMaybe (guard . (== ButtonStateDown)) evBtnState
performEvent_ $ ffor evBtnPressed $ const $ liftIO $ putStrLn "Button pressed!"
------------------------------------------------------------------------------
-- Ghosty trail of squares
------------------------------------------------------------------------------
-- Gather all mouse motion events into a list, then commit a commitLayers that
-- renders each move as a quarter alpha'd yello or cyan square.
evMouseMove <- getMouseMotionEvent
dMoves <- foldDyn (\x xs -> take 100 $ x : xs) [] evMouseMove
commitLayer $ ffor dMoves $ \moves ->
forM_ (reverse moves) $ \dat -> do
let P pos = fromIntegral <$> mouseMotionEventPos dat
color = if null (mouseMotionEventState dat)
then V4 255 255 0 128
else V4 0 255 255 128
renderAABB r color pos
------------------------------------------------------------------------------
-- Up and down squares
------------------------------------------------------------------------------
-- Get any mouse button event and accumulate them as a list of
-- AABBs. Commit a commitLayers of those rendered up/down AABBs.
evMouseButton <- getMouseButtonEvent
dBtns <- foldDyn (\x xs -> take 100 $ x : xs) [] evMouseButton
commitLayer $ ffor dBtns $ \btns ->
forM_ (reverse btns) $ \dat -> do
let AABB motion pos = mouseButtonToAABB dat
color = motionToColor motion
renderAABB r color pos
------------------------------------------------------------------------------
-- An ephemeral commitLayers that only renders when a key is down, and only listens
-- to the tick event while that key is down.
-- This is an example of the higher-order nature of the reflex network. We
-- can update the shape of the network in response to events within it.
------------------------------------------------------------------------------
evKey <- getKeyboardEvent
let evKeyNoRepeat = fmapMaybe (\k -> k <$ guard (not $ keyboardEventRepeat k)) evKey
dPressed <- holdDyn False $ (== Pressed) . keyboardEventKeyMotion <$> evKeyNoRepeat
void $ holdView (return ()) $ ffor (updated dPressed) $ \case
False -> return ()
True -> do
evDeltaTick <- getDeltaTickEvent
dTimePressed <- foldDyn (+) 0 evDeltaTick
commitLayer $ ffor dTimePressed $ \t -> do
let wrap :: Float -> Int
wrap x = if x > 255 then wrap (x - 255) else floor x
rc = wrap $ fromIntegral t/1000 * 255
gc = wrap $ fromIntegral t/2000 * 255
bc = wrap $ fromIntegral t/3000 * 255
color :: V4 Int
color = fromIntegral <$> V4 rc gc bc 255
renderAABB r color 100
------------------------------------------------------------------------------
-- Test our recurring timer events
------------------------------------------------------------------------------
let performDeltaSecondTimer n = do
evDelta <- performEventDelta =<< tickLossyFromPostBuildTime n
dTicks <- foldDyn (+) 0 $ (1 :: Int) <$ evDelta
dDelta <- holdDyn 0 evDelta
dElapsed <- foldDyn (+) 0 evDelta
flip putDebugLnE id $ updated $ do
tickz <- dTicks
lapse <- dElapsed
delta <- dDelta
return $ unwords [ show n
, "timer -"
, show tickz
, "ticks -"
, show lapse
, "lapsed -"
, show delta
, "delta since last tick"
]
performDeltaSecondTimer 1
------------------------------------------------------------------------------
-- Quit on a quit event
------------------------------------------------------------------------------
evQuit <- getQuitEvent
performEvent_ $ liftIO (putStrLn "bye!") <$ evQuit
shutdownOn =<< delay 0 evQuit
app :: (ReflexSDL2 t m, MonadReader Renderer m) => m ()
app = do
(_, dynLayers) <- runDynamicWriterT guest
r <- ask
performEvent_ $ ffor (updated dynLayers) $ \layers -> do
rendererDrawColor r $= V4 0 0 0 255
clear r
sequence_ layers
present r
main :: IO ()
main = do
initializeAll
let ogl = defaultOpenGL{ glProfile = Core Debug 3 3 }
cfg = defaultWindow{ windowGraphicsContext = OpenGLContext ogl
, windowResizable = True
, windowHighDPI = False
, windowInitialSize = V2 640 480
}
window <- createWindow "reflex-sdl2-exe" cfg
void $ glCreateContext window
putStrLn "creating renderer..."
r <- createRenderer window (-1) defaultRenderer
rendererDrawBlendMode r $= BlendAlphaBlend
-- Host the network with an example of how to embed your own effects.
-- In this case it's a simple reader.
host $ runReaderT app r
destroyRenderer r
destroyWindow window
quit