diff options
author | 3gg <3gg@shellblade.net> | 2024-12-30 12:48:31 -0800 |
---|---|---|
committer | 3gg <3gg@shellblade.net> | 2024-12-30 12:48:31 -0800 |
commit | 34cac097d15cdd7ef0a0de8b9024db9acfe8618d (patch) | |
tree | fa573d4d4436abfa83b940938c385e929dbabd06 | |
parent | d30b146ce320a48d58d37a8f191daa4ef29fbc67 (diff) |
Move window setup to App.
-rw-r--r-- | Demos/Pong/Main.hs | 9 | ||||
-rw-r--r-- | Spear/App.hs | 53 | ||||
-rw-r--r-- | Spear/Window.hs | 61 |
3 files changed, 65 insertions, 58 deletions
diff --git a/Demos/Pong/Main.hs b/Demos/Pong/Main.hs index 66fa223..de8e6f2 100644 --- a/Demos/Pong/Main.hs +++ b/Demos/Pong/Main.hs | |||
@@ -26,14 +26,13 @@ data GameState = GameState | |||
26 | , world :: [GameObject] | 26 | , world :: [GameObject] |
27 | } | 27 | } |
28 | 28 | ||
29 | options = defaultAppOptions { title = "Pong" } | ||
29 | 30 | ||
30 | app = App defaultAppOptions step render resize | 31 | app = App options initGame endGame step render resize |
31 | 32 | ||
32 | 33 | ||
33 | main = | 34 | main :: IO () |
34 | withWindow (1920, 1200) (Just "Pong") initGame endGame $ | 35 | main = runApp app |
35 | loop app | ||
36 | |||
37 | 36 | ||
38 | initGame :: Window -> Game () GameState | 37 | initGame :: Window -> Game () GameState |
39 | initGame window = do | 38 | initGame window = do |
diff --git a/Spear/App.hs b/Spear/App.hs index 7f23359..61ea3b1 100644 --- a/Spear/App.hs +++ b/Spear/App.hs | |||
@@ -1,11 +1,13 @@ | |||
1 | module Spear.App | 1 | module Spear.App |
2 | ( App(..), | 2 | ( App(..) |
3 | Elapsed, | 3 | , AppOptions(..) |
4 | Dt, | 4 | , Elapsed |
5 | Step, | 5 | , Dt |
6 | defaultAppOptions, | 6 | , Step |
7 | loop, | 7 | , defaultAppOptions |
8 | ) | 8 | , runApp |
9 | , loop | ||
10 | ) | ||
9 | where | 11 | where |
10 | 12 | ||
11 | import Control.Monad | 13 | import Control.Monad |
@@ -26,26 +28,45 @@ type Step s = Elapsed -> Dt -> [InputEvent] -> Game s Bool | |||
26 | 28 | ||
27 | -- | Application options. | 29 | -- | Application options. |
28 | data AppOptions = AppOptions | 30 | data AppOptions = AppOptions |
29 | { maxFPS :: Int | 31 | { title :: String |
32 | , windowWidth :: Int | ||
33 | , windowHeight :: Int | ||
34 | , maxFPS :: Int | ||
30 | , animationFPS :: Int | 35 | , animationFPS :: Int |
31 | , enableProfiling :: Bool | 36 | , enableProfiling :: Bool |
32 | } | 37 | } |
33 | 38 | ||
34 | defaultAppOptions = AppOptions | 39 | defaultAppOptions = AppOptions |
35 | { maxFPS = 0 -- If non-zero, cap frame rate to this value. | 40 | { title = "Spear Application" |
41 | , windowWidth = 1920 | ||
42 | , windowHeight = 1080 | ||
43 | , maxFPS = 0 -- If non-zero, cap frame rate to this value. | ||
36 | , animationFPS = 60 -- If non-zero, use fixed time step animation. | 44 | , animationFPS = 60 -- If non-zero, use fixed time step animation. |
37 | , enableProfiling = False | 45 | , enableProfiling = False |
38 | } | 46 | } |
39 | 47 | ||
40 | -- | Application state. | 48 | -- | Application state. |
41 | data App s = App | 49 | data App s = App |
42 | { options :: AppOptions | 50 | { appOptions :: AppOptions |
43 | , stepApp :: Step s | 51 | , initApp :: Window -> Game () s |
44 | , renderApp :: Game s () | 52 | , endApp :: Game s () |
45 | , resizeApp :: WindowEvent -> Game s () | 53 | , stepApp :: Step s |
54 | , renderApp :: Game s () | ||
55 | , resizeApp :: WindowEvent -> Game s () | ||
46 | } | 56 | } |
47 | 57 | ||
48 | 58 | ||
59 | -- | Run the application. | ||
60 | runApp :: App s -> IO () | ||
61 | runApp app = | ||
62 | let ops = appOptions app | ||
63 | w = windowWidth ops | ||
64 | h = windowHeight ops | ||
65 | in withWindow (w, h) (title ops) $ \window -> flip evalGame () $ do | ||
66 | gameState <- initApp app window | ||
67 | (result, endGameState) <- runSubGame (loop app window) gameState | ||
68 | runSubGame' (endApp app) endGameState | ||
69 | |||
49 | -- | Convert FPS to desired delta time. | 70 | -- | Convert FPS to desired delta time. |
50 | fpsToDdt :: Int -> TimeDelta | 71 | fpsToDdt :: Int -> TimeDelta |
51 | fpsToDdt fps = if fps > 0 then secToTimeDelta (1.0 / fromIntegral fps) else 0 | 72 | fpsToDdt fps = if fps > 0 then secToTimeDelta (1.0 / fromIntegral fps) else 0 |
@@ -59,8 +80,8 @@ loop app window = do | |||
59 | resizeApp app (ResizeEvent width height) | 80 | resizeApp app (ResizeEvent width height) |
60 | renderApp app | 81 | renderApp app |
61 | 82 | ||
62 | let ddt = fpsToDdt . maxFPS . options $ app -- Desired render time step. | 83 | let ddt = fpsToDdt . maxFPS . appOptions $ app -- Desired render time step. |
63 | let animationDdt = fpsToDdt . animationFPS . options $ app -- Desired animation time step. | 84 | let animationDdt = fpsToDdt . animationFPS . appOptions $ app -- Desired animation time step. |
64 | 85 | ||
65 | timer <- gameIO newTimer | 86 | timer <- gameIO newTimer |
66 | gameIO $ Timer.start timer | 87 | gameIO $ Timer.start timer |
@@ -121,7 +142,7 @@ loop' window renderDdt animationDdt lastAnimationTime inputTimer app = do | |||
121 | 142 | ||
122 | -- Limit frame rate if so requested by the application. | 143 | -- Limit frame rate if so requested by the application. |
123 | -- This currently makes the rendering stutter and is not very desirable. | 144 | -- This currently makes the rendering stutter and is not very desirable. |
124 | when ((maxFPS . options $ app) > 0) $ do | 145 | when ((maxFPS . appOptions $ app) > 0) $ do |
125 | frameEnd <- gameIO now | 146 | frameEnd <- gameIO now |
126 | let ddt = renderDdt | 147 | let ddt = renderDdt |
127 | let frameTime = timeDiff (lastTick timer) frameEnd | 148 | let frameTime = timeDiff (lastTick timer) frameEnd |
diff --git a/Spear/Window.hs b/Spear/Window.hs index 65ca243..2dcd1fa 100644 --- a/Spear/Window.hs +++ b/Spear/Window.hs | |||
@@ -7,7 +7,6 @@ module Spear.Window | |||
7 | Window, | 7 | Window, |
8 | Width, | 8 | Width, |
9 | Height, | 9 | Height, |
10 | Init, | ||
11 | WindowEvent(..), | 10 | WindowEvent(..), |
12 | withWindow, | 11 | withWindow, |
13 | pollWindowEvents, | 12 | pollWindowEvents, |
@@ -21,10 +20,10 @@ module Spear.Window | |||
21 | whenKeyUp, | 20 | whenKeyUp, |
22 | processKeys, | 21 | processKeys, |
23 | processButtons, | 22 | processButtons, |
24 | InputEvent (..), | 23 | InputEvent(..), |
25 | Key (..), | 24 | Key(..), |
26 | MouseButton (..), | 25 | MouseButton(..), |
27 | MouseProp (..), | 26 | MouseProp(..), |
28 | MousePos, | 27 | MousePos, |
29 | MouseDelta, | 28 | MouseDelta, |
30 | ) | 29 | ) |
@@ -52,12 +51,6 @@ type Dimensions = (Width, Height) | |||
52 | 51 | ||
53 | type WindowTitle = String | 52 | type WindowTitle = String |
54 | 53 | ||
55 | -- | Game initialiser. | ||
56 | type Init s = Window -> Game () s | ||
57 | |||
58 | -- | Game finalizer. | ||
59 | type End s = Game s () | ||
60 | |||
61 | -- | Window exception. | 54 | -- | Window exception. |
62 | newtype WindowException = WindowException String deriving (Show) | 55 | newtype WindowException = WindowException String deriving (Show) |
63 | 56 | ||
@@ -85,28 +78,22 @@ data Window = Window | |||
85 | 78 | ||
86 | withWindow :: | 79 | withWindow :: |
87 | Dimensions -> | 80 | Dimensions -> |
88 | Maybe WindowTitle -> | 81 | WindowTitle -> |
89 | Init s -> | 82 | (Window -> IO a) -> |
90 | End s -> | ||
91 | (Window -> Game s a) -> | ||
92 | IO a | 83 | IO a |
93 | withWindow dim@(w, h) windowTitle init end run = do | 84 | withWindow dim@(w, h) windowTitle run = do |
94 | flip evalGame () $ do | 85 | window <- do |
95 | window <- gameIO $ do | 86 | success <- GLFW.init |
96 | success <- GLFW.init | 87 | unless success $ throw (WindowException "GLFW.initialize failed") |
97 | unless success $ throw (WindowException "GLFW.initialize failed") | 88 | setup dim windowTitle |
98 | setup dim windowTitle | 89 | result <- run window |
99 | gameState <- init window | 90 | GLFW.destroyWindow $ glfwWindow window |
100 | (result, endGameState) <- runSubGame (run window) gameState | 91 | GLFW.terminate |
101 | runSubGame' end endGameState | 92 | return result |
102 | gameIO $ do | ||
103 | GLFW.destroyWindow $ glfwWindow window | ||
104 | GLFW.terminate | ||
105 | return result | ||
106 | 93 | ||
107 | setup :: | 94 | setup :: |
108 | Dimensions -> | 95 | Dimensions -> |
109 | Maybe WindowTitle -> | 96 | WindowTitle -> |
110 | IO Window | 97 | IO Window |
111 | setup (w, h) windowTitle = do | 98 | setup (w, h) windowTitle = do |
112 | closeRequest <- newEmptyMVar | 99 | closeRequest <- newEmptyMVar |
@@ -118,7 +105,7 @@ setup (w, h) windowTitle = do | |||
118 | GLFW.windowHint $ GLFW.WindowHint'ContextVersionMajor major | 105 | GLFW.windowHint $ GLFW.WindowHint'ContextVersionMajor major |
119 | GLFW.windowHint $ GLFW.WindowHint'ContextVersionMinor minor | 106 | GLFW.windowHint $ GLFW.WindowHint'ContextVersionMinor minor |
120 | when (major >= 3) $ GLFW.windowHint $ GLFW.WindowHint'OpenGLProfile GLFW.OpenGLProfile'Core | 107 | when (major >= 3) $ GLFW.windowHint $ GLFW.WindowHint'OpenGLProfile GLFW.OpenGLProfile'Core |
121 | GLFW.createWindow w h (fromMaybe "Spear" windowTitle) Nothing Nothing | 108 | GLFW.createWindow w h windowTitle Nothing Nothing |
122 | 109 | ||
123 | unless (isJust maybeWindow) | 110 | unless (isJust maybeWindow) |
124 | $ throwIO (WindowException "GLFW.openWindow failed") | 111 | $ throwIO (WindowException "GLFW.openWindow failed") |
@@ -141,7 +128,7 @@ setup (w, h) windowTitle = do | |||
141 | pollInputEvents :: Window -> IO [InputEvent] | 128 | pollInputEvents :: Window -> IO [InputEvent] |
142 | pollInputEvents window = do | 129 | pollInputEvents window = do |
143 | GLFW.pollEvents | 130 | GLFW.pollEvents |
144 | getEvents (inputEventsMVar window) | 131 | getEvents (inputEventsMVar window) |
145 | 132 | ||
146 | -- | Poll for window events. | 133 | -- | Poll for window events. |
147 | pollWindowEvents :: Window -> IO [WindowEvent] | 134 | pollWindowEvents :: Window -> IO [WindowEvent] |
@@ -206,23 +193,23 @@ replaceMVar mvar val = tryTakeMVar mvar >> putMVar mvar val | |||
206 | addEvent :: MVar [a] -> a -> IO () | 193 | addEvent :: MVar [a] -> a -> IO () |
207 | addEvent mvar val = | 194 | addEvent mvar val = |
208 | tryTakeMVar mvar >>= \xs -> case xs of | 195 | tryTakeMVar mvar >>= \xs -> case xs of |
209 | Nothing -> putMVar mvar [val] -- >> (putStrLn $ show val) | 196 | Nothing -> putMVar mvar [val] |
210 | Just events -> putMVar mvar (val : events) -- >> (putStrLn $ show (val:events)) | 197 | Just events -> putMVar mvar (val : events) |
211 | 198 | ||
212 | -- Input | 199 | -- Input |
213 | 200 | ||
214 | -- | Run the game action when the key is down. | 201 | -- | Run the game action when the key is down. |
215 | whenKeyDown :: GLFW.Window -> Key -> Game s a -> Game s () | 202 | whenKeyDown :: GLFW.Window -> Key -> Game s () -> Game s () |
216 | whenKeyDown = whenKeyInState GLFW.KeyState'Pressed | 203 | whenKeyDown = whenKeyInState GLFW.KeyState'Pressed |
217 | 204 | ||
218 | -- | Run the game action when the key is up. | 205 | -- | Run the game action when the key is up. |
219 | whenKeyUp :: GLFW.Window -> Key -> Game s a -> Game s () | 206 | whenKeyUp :: GLFW.Window -> Key -> Game s () -> Game s () |
220 | whenKeyUp = whenKeyInState GLFW.KeyState'Released | 207 | whenKeyUp = whenKeyInState GLFW.KeyState'Released |
221 | 208 | ||
222 | whenKeyInState :: GLFW.KeyState -> GLFW.Window -> Key -> Game s a -> Game s () | 209 | whenKeyInState :: GLFW.KeyState -> GLFW.Window -> Key -> Game s () -> Game s () |
223 | whenKeyInState state window key game = do | 210 | whenKeyInState state window key game = do |
224 | isDown <- fmap (==state) $ gameIO . GLFW.getKey window . toGLFWkey $ key | 211 | isDown <- fmap (==state) $ gameIO . GLFW.getKey window . toGLFWkey $ key |
225 | when isDown $ void game | 212 | when isDown game |
226 | 213 | ||
227 | -- | Check whether the given keys are pressed and return the value associated | 214 | -- | Check whether the given keys are pressed and return the value associated |
228 | -- with each of the pressed keys. | 215 | -- with each of the pressed keys. |