helm 0.2.0 → 0.3.0
raw patch · 11 files changed
+482/−111 lines, 11 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ FRP.Helm.Automaton: (<<<) :: Automaton b c -> Automaton a b -> Automaton a c
+ FRP.Helm.Automaton: (>>>) :: Automaton a b -> Automaton b c -> Automaton a c
+ FRP.Helm.Automaton: Step :: (a -> (Automaton a b, b)) -> Automaton a b
+ FRP.Helm.Automaton: combine :: [Automaton a b] -> Automaton a [b]
+ FRP.Helm.Automaton: counter :: Automaton a Int
+ FRP.Helm.Automaton: data Automaton a b
+ FRP.Helm.Automaton: pure :: (a -> b) -> Automaton a b
+ FRP.Helm.Automaton: run :: Automaton a b -> b -> SignalGen (Signal a) -> SignalGen (Signal b)
+ FRP.Helm.Automaton: stateful :: b -> (a -> b -> b) -> Automaton a b
+ FRP.Helm.Automaton: step :: a -> Automaton a b -> (Automaton a b, b)
+ FRP.Helm.Graphics: Text :: String -> Color -> String -> Double -> FontWeight -> FontSlant -> Text
+ FRP.Helm.Graphics: TextElement :: Text -> Element
+ FRP.Helm.Graphics: data Text
+ FRP.Helm.Graphics: fontSize :: Text -> Double
+ FRP.Helm.Graphics: fontSlant :: Text -> FontSlant
+ FRP.Helm.Graphics: fontTypeface :: Text -> String
+ FRP.Helm.Graphics: fontWeight :: Text -> FontWeight
+ FRP.Helm.Graphics: textColor :: Text -> Color
+ FRP.Helm.Graphics: textUTF8 :: Text -> String
+ FRP.Helm.Joystick: available :: SignalGen (Signal Int)
+ FRP.Helm.Joystick: availableAxes :: Joystick -> SignalGen (Signal Int)
+ FRP.Helm.Joystick: availableBalls :: Joystick -> SignalGen (Signal Int)
+ FRP.Helm.Joystick: availableButtons :: Joystick -> SignalGen (Signal Int)
+ FRP.Helm.Joystick: availableHats :: Joystick -> SignalGen (Signal Int)
+ FRP.Helm.Joystick: axis :: Joystick -> Int -> SignalGen (Signal Int)
+ FRP.Helm.Joystick: ball :: Joystick -> Int -> SignalGen (Signal (Int, Int))
+ FRP.Helm.Joystick: button :: Joystick -> Int -> SignalGen (Signal Bool)
+ FRP.Helm.Joystick: hat :: Joystick -> Int -> SignalGen (Signal (Int, Int))
+ FRP.Helm.Joystick: index :: Joystick -> SignalGen (Signal Int)
+ FRP.Helm.Joystick: name :: Int -> SignalGen (Signal String)
+ FRP.Helm.Joystick: open :: Int -> SignalGen (Signal Joystick)
+ FRP.Helm.Joystick: type Joystick = Joystick
+ FRP.Helm.Mouse: instance Enum Mouse
+ FRP.Helm.Text: asText :: Show a => a -> Element
+ FRP.Helm.Text: bold :: Text -> Text
+ FRP.Helm.Text: color :: Color -> Text -> Text
+ FRP.Helm.Text: defaultText :: Text
+ FRP.Helm.Text: header :: Text -> Text
+ FRP.Helm.Text: height :: Double -> Text -> Text
+ FRP.Helm.Text: italic :: Text -> Text
+ FRP.Helm.Text: monospace :: Text -> Text
+ FRP.Helm.Text: plainText :: String -> Element
+ FRP.Helm.Text: text :: Text -> Element
+ FRP.Helm.Text: toText :: String -> Text
+ FRP.Helm.Text: typeface :: String -> Text -> Text
Files
- FRP/Helm.hs +20/−14
- FRP/Helm/Automaton.hs +78/−0
- FRP/Helm/Color.hs +27/−27
- FRP/Helm/Graphics.hs +23/−10
- FRP/Helm/Joystick.hs +105/−0
- FRP/Helm/Keyboard.hs +5/−5
- FRP/Helm/Mouse.hs +19/−15
- FRP/Helm/Text.hs +91/−0
- FRP/Helm/Window.hs +5/−4
- README.md +104/−34
- helm.cabal +5/−2
FRP/Helm.hs view
@@ -12,6 +12,7 @@ module FRP.Helm.Graphics, ) where +import Control.Monad (void) import Data.IORef import Foreign.Ptr (castPtr) import FRP.Elerea.Simple@@ -25,7 +26,6 @@ {-| Attempt to change the window dimensions (and initialize the video mode if not already). Will try to get a hardware accelerated window and then fallback to a software one. Throws an exception if the software mode can't be used as a fallback. -}--- TODO: userland version of this requestDimensions :: Int -> Int -> IO SDL.Surface requestDimensions w h = do mayhaps <- SDL.trySetVideoMode w h 32 [SDL.HWSurface, SDL.DoubleBuf, SDL.Resizable]@@ -83,7 +83,7 @@ > return $ fmap render dims -} run :: SignalGen (Signal Element) -> IO ()-run gen = SDL.init [SDL.InitVideo] >> requestDimensions 800 600 >> start gen >>= newEngineState >>= run'+run gen = SDL.init [SDL.InitVideo, SDL.InitJoystick] >> requestDimensions 800 600 >> start gen >>= newEngineState >>= run' {-| A utility function called by 'run' that samples the element or quits the entire engine if SDL events say to do so. -}@@ -160,7 +160,7 @@ {-| A utility function for rendering a specific element. -} renderElement :: EngineState -> Element -> Cairo.Render ()-renderElement state (CollageElement _ _ forms) = mapM (renderForm state) forms >> return ()+renderElement state (CollageElement _ _ forms) = void $ mapM_ (renderForm state) forms renderElement state (ImageElement (sx, sy) sw sh src stretch) = do (surface, w, h) <- Cairo.liftIO $ getSurface state (normalise src) @@ -178,6 +178,12 @@ Cairo.fill Cairo.restore +renderElement _ (TextElement (Text { textColor = (Color r g b a), .. })) = do+ Cairo.setSourceRGBA r g b a+ Cairo.selectFontFace fontTypeface fontSlant fontWeight+ Cairo.setFontSize fontSize+ Cairo.showText textUTF8+ {-| A utility function that goes into a state of transformation and then pops it when finished. -} withTransform :: Double -> Double -> Double -> Double -> Cairo.Render () -> Cairo.Render () withTransform s t x y f = Cairo.save >> Cairo.scale s s >> Cairo.rotate t >> Cairo.translate x y >> f >> Cairo.restore@@ -212,31 +218,31 @@ setFillStyle :: EngineState -> FillStyle -> Cairo.Render () setFillStyle _ (Solid (Color r g b a)) = Cairo.setSourceRGBA r g b a >> Cairo.fill setFillStyle state (Texture src) = do- (surface, w, h) <- Cairo.liftIO $ getSurface state (normalise src)+ (surface, _, _) <- Cairo.liftIO $ getSurface state (normalise src) - Cairo.setSourceSurface surface 0 0 >> Cairo.getSource >>= (flip Cairo.patternSetExtend) Cairo.ExtendRepeat+ Cairo.setSourceSurface surface 0 0 >> Cairo.getSource >>= flip Cairo.patternSetExtend Cairo.ExtendRepeat Cairo.fill -setFillStyle _ (Gradient (Linear (sx, sy) (ex, ey) points)) = do- Cairo.withLinearPattern sx sy ex ey $ \pattern -> do- Cairo.setSource pattern >> mapM (\(o, (Color r g b a)) -> Cairo.patternAddColorStopRGBA pattern o r g b a) points >> Cairo.fill+setFillStyle _ (Gradient (Linear (sx, sy) (ex, ey) points)) =+ Cairo.withLinearPattern sx sy ex ey $ \pattern ->+ Cairo.setSource pattern >> mapM (\(o, Color r g b a) -> Cairo.patternAddColorStopRGBA pattern o r g b a) points >> Cairo.fill -setFillStyle _ (Gradient (Radial (sx, sy) sr (ex, ey) er points)) = do- Cairo.withRadialPattern sx sy sr ex ey er $ \pattern -> do- Cairo.setSource pattern >> mapM (\(o, (Color r g b a)) -> Cairo.patternAddColorStopRGBA pattern o r g b a) points >> Cairo.fill+setFillStyle _ (Gradient (Radial (sx, sy) sr (ex, ey) er points)) =+ Cairo.withRadialPattern sx sy sr ex ey er $ \pattern ->+ Cairo.setSource pattern >> mapM (\(o, Color r g b a) -> Cairo.patternAddColorStopRGBA pattern o r g b a) points >> Cairo.fill {-| A utility that renders a form. -} renderForm :: EngineState -> Form -> Cairo.Render () renderForm _ (Form { style = PathForm style p, .. }) = withTransform scalar theta x y $ - setLineStyle style >> Cairo.moveTo hx hy >> mapM (\(x_, y_) -> Cairo.lineTo x_ y_) p >> return ()+ void $ setLineStyle style >> Cairo.moveTo hx hy >> mapM (uncurry Cairo.lineTo) p where (hx, hy) = head p renderForm state (Form { style = ShapeForm style (PolygonShape points), .. }) = withTransform scalar theta x y $ do- Cairo.newPath >> Cairo.moveTo hx hy >> mapM (\(x_, y_) -> Cairo.lineTo x_ y_) points >> Cairo.closePath+ Cairo.newPath >> Cairo.moveTo hx hy >> mapM (uncurry Cairo.lineTo) points >> Cairo.closePath case style of Left lineStyle -> setLineStyle lineStyle@@ -264,4 +270,4 @@ Right fillStyle -> setFillStyle state fillStyle renderForm state (Form { style = ElementForm element, .. }) = withTransform scalar theta x y $ renderElement state element-renderForm state (Form { style = GroupForm m forms, .. }) = withTransform scalar theta x y $ Cairo.setMatrix m >> mapM (renderForm state) forms >> return ()+renderForm state (Form { style = GroupForm m forms, .. }) = withTransform scalar theta x y $ void $ Cairo.setMatrix m >> mapM (renderForm state) forms
+ FRP/Helm/Automaton.hs view
@@ -0,0 +1,78 @@+{-| Contains all data structures and functions for composing, calculating and creating automatons. -}+module FRP.Helm.Automaton (+ -- * Types+ Automaton(..),+ -- * Composing+ pure,+ stateful,+ combine,+ (>>>),+ (<<<),+ -- * Computing+ step,+ run,+ counter+) where++import FRP.Elerea.Simple (Signal, SignalGen, transfer)++{-| A data structure describing an automaton.+ An automaton is essentially a high-level way to package piped behavior+ between an input signal and an output signal. Automatons can also+ be composed, allowing you to connect one automaton to another+ and pipe data between them. Automatons are an easy and powerful way+ to create composable dynamic behavior, like animation systems. -}+data Automaton a b = Step (a -> (Automaton a b, b))++{-| Creates a pure automaton that has no accumulated state. It applies input to+ a function at each step. -}+pure :: (a -> b) -> Automaton a b+pure f = Step (\x -> (pure f, f x))++{-| Creates an automaton that has an initial and accumulated state. It applies+ input and the last state to a function at each step. -}+stateful :: b -> (a -> b -> b) -> Automaton a b+stateful s f = Step (\x -> let s' = f x s+ in (stateful s' f, s'))++{-| Steps an automaton forward, returning the next automaton to step+ and output of the step in a tuple. -}+step :: a -> Automaton a b -> (Automaton a b, b)+step auto (Step f) = f auto++{-| Combines a list of automatons that take some input+ and turns it into an automaton that takes+ the same input and outputs a list of all outputs+ from each separate automaton. -}+combine :: [Automaton a b] -> Automaton a [b]+combine autos =+ Step (\a -> let (autos', bs) = unzip $ map (step a) autos+ in (combine autos', bs))++{-| Pipes two automatons together. It essentially+ returns an automaton that takes the input of the first+ automaton and outputs the output of the second automaton,+ with the directly connected values being discarded. -}+(>>>) :: Automaton a b -> Automaton b c -> Automaton a c+f >>> g =+ Step (\a -> let (f', b) = step a f+ (g', c) = step b g+ in (f' >>> g', c))++{-| Pipes two automatons in the opposite order of '>>>'. -}+(<<<) :: Automaton b c -> Automaton a b -> Automaton a c+g <<< f = f >>> g++{-| A useful automaton that outputs the amount of times it has been stepped,+ discarding its input value. -}+counter :: Automaton a Int+counter = stateful 0 (\_ c -> c + 1)++{-| Runs an automaton with an initial output value and input signal generator+ and creates an output signal generator that contains a signal that can be+ sampled for the output value. -}+run :: Automaton a b -> b -> SignalGen (Signal a) -> SignalGen (Signal b)+run auto initial feeder = do+ stepper <- feeder >>= transfer (auto, initial) (\a (Step f, _) -> f a)++ return $ fmap snd stepper
FRP/Helm/Color.hs view
@@ -1,34 +1,34 @@ {-| Contains all data structures and functions for composing colors. -} module FRP.Helm.Color (- -- * Types- Color(..),- Gradient(..),- -- * Composing- rgba,- rgb,+ -- * Types+ Color(..),+ Gradient(..),+ -- * Composing+ rgba,+ rgb, hsva, hsv, complement,- linear,- radial,- -- * Constants- red,- lime,- blue,- yellow,- cyan,- magenta,- black,- white,- gray,- grey,- maroon,- navy,- green,- teal,- purple,- violet,- forestGreen+ linear,+ radial,+ -- * Constants+ red,+ lime,+ blue,+ yellow,+ cyan,+ magenta,+ black,+ white,+ gray,+ grey,+ maroon,+ navy,+ green,+ teal,+ purple,+ violet,+ forestGreen ) where {-| A data structure describing a color. It is represented interally as an RGBA@@ -138,7 +138,7 @@ where h' = h / 60- h'' = (floor h') `mod` 6 :: Int+ h'' = floor h' `mod` 6 :: Int f = h' - fromIntegral h'' p = v * (1 - s) q = v * (1 - f * s)
FRP/Helm/Graphics.hs view
@@ -3,6 +3,7 @@ module FRP.Helm.Graphics ( -- * Types Element(..),+ Text(..), Form(..), FormStyle(..), FillStyle(..),@@ -49,8 +50,9 @@ ngon ) where -import FRP.Helm.Color as Color+import FRP.Helm.Color (Color, black, Gradient) import Graphics.Rendering.Cairo.Matrix (Matrix, identity)+import qualified Graphics.Rendering.Cairo as Cairo {-| A data structure describing something that can be rendered to the screen. Elements are the most important structure@@ -60,8 +62,19 @@ off to the 'collage' function, which essentially renders a collection of forms together. -} data Element = CollageElement Int Int [Form] |- ImageElement (Int, Int) Int Int FilePath Bool+ ImageElement (Int, Int) Int Int FilePath Bool |+ TextElement Text +{-| A data structure describing a piece of formatted text. -}+data Text = Text {+ textUTF8 :: String,+ textColor :: Color,+ fontTypeface :: String,+ fontSize :: Double,+ fontWeight :: Cairo.FontWeight,+ fontSlant :: Cairo.FontSlant+}+ {-| Create an element from an image with a given width, height and image file path. If the image dimensions are not the same as given, then it will stretch/shrink to fit. Only PNG files are supported currently. -}@@ -116,7 +129,7 @@ flat caps and regular sharp joints. -} defaultLine :: LineStyle defaultLine = LineStyle {- color = Color.black,+ color = black, width = 1, cap = Flat, join = Sharp 10,@@ -153,15 +166,15 @@ {-| Creates a form from a shape by filling it with a specific color. -} filled :: Color -> Shape -> Form-filled color shape = fill (Solid color) shape+filled color = fill (Solid color) {-| Creates a form from a shape with a tiled texture and image file path. -} textured :: String -> Shape -> Form-textured src shape = fill (Texture src) shape+textured src = fill (Texture src) {-| Creates a form from a shape filled with a gradient. -} gradient :: Gradient -> Shape -> Form-gradient grad shape = fill (Gradient grad) shape+gradient grad = fill (Gradient grad) {-| Creates a form from a shape by outlining it with a specific line style. -} outlined :: LineStyle -> Shape -> Form@@ -202,11 +215,11 @@ {-| Moves a form's x-coordinate relative to its current position. -} moveX :: Double -> Form -> Form-moveX x f = move (x, 0) f+moveX x = move (x, 0) {-| Moves a form's y-coordinate relative to its current position. -} moveY :: Double -> Form -> Form-moveY y f = move (0, y) f+moveY y = move (0, y) {-| Create an element from a collection of forms, with width and height arguments. Can be used to directly render a collection of forms.@@ -215,7 +228,7 @@ > move (100, 100) $ outlined (solid white) $ circle 50] -} collage :: Int -> Int -> [Form] -> Element-collage w h forms = CollageElement w h forms+collage = CollageElement {-| A data type made up a collection of points that form a path when joined. -} type Path = [(Double, Double)]@@ -234,7 +247,7 @@ {-| Creates a shape from a path (a set of points). -} polygon :: Path -> Shape-polygon points = PolygonShape points+polygon = PolygonShape {-| Creates a rectangular shape with a width and height. -} rect :: Double -> Double -> Shape
+ FRP/Helm/Joystick.hs view
@@ -0,0 +1,105 @@+{-| Contains signals that sample input from joysticks. -}+module FRP.Helm.Joystick (+ -- * Types+ Joystick,+ -- * Probing+ available,+ name,+ open,+ index,+ availableAxes,+ availableBalls,+ availableHats,+ availableButtons,+ -- * Joystick State+ axis,+ hat,+ button,+ ball+) where++import Control.Applicative+import Data.Int (Int16)+import FRP.Elerea.Simple+import qualified Graphics.UI.SDL as SDL++{-| A type describing a joystick. -}+type Joystick = SDL.Joystick++{-| The amount of joysticks available. -}+available :: SignalGen (Signal Int)+available = effectful SDL.countAvailable++{-| The name of a joystick. -}+name :: Int -> SignalGen (Signal String)+name i = effectful $ SDL.name i++{-| The joystick at a certain slot. -}+open :: Int -> SignalGen (Signal Joystick)+open i = effectful $ SDL.open i++{-| The index of a joystick. -}+index :: Joystick -> SignalGen (Signal Int)+index j = return $ return $ SDL.index j++{-| The amount of axes available for a joystick. -}+availableAxes :: Joystick -> SignalGen (Signal Int)+availableAxes j = return $ return $ SDL.axesAvailable j++{-| The amount of balls available for a joystick. -}+availableBalls :: Joystick -> SignalGen (Signal Int)+availableBalls j = return $ return $ SDL.ballsAvailable j++{-| The amount of hats available for a joystick. -}+availableHats :: Joystick -> SignalGen (Signal Int)+availableHats j = return $ return $ SDL.hatsAvailable j++{-| The amount of buttons available for a joystick. -}+availableButtons :: Joystick -> SignalGen (Signal Int)+availableButtons j = return $ return $ SDL.buttonsAvailable j++{-| The current state of the axis of the joystick. -}+axis :: Joystick -> Int -> SignalGen (Signal Int)+axis j i = effectful $ SDL.update >> fromIntegral <$> SDL.getAxis j (fromIntegral i)++{-| The current state of the hat of the joystick, returned+ as a directional tuple. For example, up is /(0, -1)/,+ left /(-1, 0)/, bottom-right is /(1, 1)/, etc. -}+hat :: Joystick -> Int -> SignalGen (Signal (Int, Int))+hat j i = effectful $ SDL.update >> hat' <$> SDL.getHat j (fromIntegral i)++{-| A utility function for mapping a list of hat states to an averaged directional tuple. -}+hat' :: [SDL.Hat] -> (Int, Int)+hat' hats = if l > 0 then (round $ fromIntegral hx / l, round $ fromIntegral hy / l) else (0, 0)+ where+ l = realToFrac $ length hats :: Double+ (hx, hy) = foldl hat'' (0, 0) hats++{-| A utility function for accumulating the total directional tuple. -}+hat'' :: (Int, Int) -> SDL.Hat -> (Int, Int)+hat'' (x, y) h =+ case h of+ SDL.HatCentered -> (x, y)+ SDL.HatUp -> (x, y - 1)+ SDL.HatRight -> (x + 1, y)+ SDL.HatDown -> (x, y + 1)+ SDL.HatLeft -> (x - 1, y)+ SDL.HatRightUp -> (x + 1, y - 1)+ SDL.HatRightDown -> (x + 1, y + 1)+ SDL.HatLeftUp -> (x - 1, x - 1)+ SDL.HatLeftDown -> (x - 1, y + 1)++{-| The current state of the button of the joystick. -}+button :: Joystick -> Int -> SignalGen (Signal Bool)+button j i = effectful $ SDL.update >> SDL.getButton j (fromIntegral i)++{-| The current state of the ball of the joystick. -}+ball :: Joystick -> Int -> SignalGen (Signal (Int, Int))+ball j i = effectful $ SDL.update >> ball' <$> SDL.getBall j (fromIntegral i)++{-| A utility function for mapping the optional value to a null tuple or the actual tuple. -}+ball' :: Maybe (Int16, Int16) -> (Int, Int)+ball' mayhaps =+ case mayhaps of+ Just (x, y) -> (fromIntegral x, fromIntegral y)+ Nothing -> (0, 0)
FRP/Helm/Keyboard.hs view
@@ -25,7 +25,7 @@ getKeyState = alloca $ \numkeysPtr -> do keysPtr <- sdlGetKeyState numkeysPtr numkeys <- peek numkeysPtr- (map fromIntegral . findIndices (== 1)) <$> peekArray (fromIntegral numkeys) keysPtr+ (map fromIntegral . elemIndices 1) <$> peekArray (fromIntegral numkeys) keysPtr {-| A data structure describing a physical key on a keyboard. -} data Key = BackspaceKey | TabKey | ClearKey | EnterKey | PauseKey | EscapeKey |@@ -329,15 +329,15 @@ {-| Whether either shift key is pressed. -} shift :: SignalGen (Signal Bool)-shift = effectful $ (elem SDL.KeyModShift) <$> SDL.getModState+shift = effectful $ elem SDL.KeyModShift <$> SDL.getModState {-| Whether either control key is pressed. -} ctrl :: SignalGen (Signal Bool)-ctrl = effectful $ (elem SDL.KeyModCtrl) <$> SDL.getModState+ctrl = effectful $ elem SDL.KeyModCtrl <$> SDL.getModState {-| Whether a key is pressed. -} isDown :: Key -> SignalGen (Signal Bool)-isDown k = effectful $ (elem $ fromEnum k) <$> getKeyState+isDown k = effectful $ elem (fromEnum k) <$> getKeyState {-| Whether the enter (a.k.a. return) key is pressed. -} enter :: SignalGen (Signal Bool)@@ -349,7 +349,7 @@ {-| A list of keys that are currently being pressed. -} keysDown :: SignalGen (Signal [Key])-keysDown = effectful $ (map toEnum) <$> getKeyState+keysDown = effectful $ map toEnum <$> getKeyState {-| A directional tuple combined from the arrow keys. When none of the arrow keys are being pressed this signal samples to /(0, 0)/, otherwise it samples to a
FRP/Helm/Mouse.hs view
@@ -1,20 +1,32 @@ {-| Contains signals that sample input from the mouse. -} module FRP.Helm.Mouse (- -- * Types- Mouse(..),- -- * Position- isDown,- -- * Mouse State- position, x, y+ -- * Types+ Mouse(..),+ -- * Position+ isDown,+ -- * Mouse State+ position, x, y ) where import Control.Applicative import FRP.Elerea.Simple import qualified Graphics.UI.SDL as SDL+import qualified Graphics.UI.SDL.Utilities as Util {-| A data structure describing a button on a mouse. -} data Mouse = LeftMouse | MiddleMouse | RightMouse +{- All integer values of this enum are equivalent to the SDL key enum. -}+instance Enum Mouse where+ fromEnum LeftMouse = 1+ fromEnum MiddleMouse = 2+ fromEnum RightMouse = 3++ toEnum 1 = LeftMouse+ toEnum 2 = MiddleMouse+ toEnum 3 = RightMouse+ toEnum _ = error "FRP.Helm.Mouse.Mouse.toEnum: bad argument"+ {-| The current position of the mouse. -} position :: SignalGen (Signal (Int, Int)) position = effectful $ (\(x_, y_, _) -> (x_, y_)) <$> SDL.getMouseState@@ -27,15 +39,7 @@ y :: SignalGen (Signal Int) y = effectful $ (\(_, y_, _) -> y_) <$> SDL.getMouseState -{-| Maps our mouse type into SDL's one. -}-mapMouse :: Mouse -> SDL.MouseButton-mapMouse m =- case m of- LeftMouse -> SDL.ButtonLeft- MiddleMouse -> SDL.ButtonMiddle- RightMouse -> SDL.ButtonRight- {-| The current state of a certain mouse button. True if the mouse is down, false otherwise. -} isDown :: Mouse -> SignalGen (Signal Bool)-isDown m = effectful $ (\(_, _, b_) -> elem (mapMouse m) b_) <$> SDL.getMouseState+isDown m = effectful $ (\(_, _, b_) -> elem (Util.toEnum $ fromIntegral $ fromEnum m) b_) <$> SDL.getMouseState
+ FRP/Helm/Text.hs view
@@ -0,0 +1,91 @@+{-| Contains all the data structures and functions for composing+ pieces of formatted text. -}+module FRP.Helm.Text (+ -- * Elements+ plainText,+ asText,+ text,+ -- * Composing+ defaultText,+ toText,+ -- * Formatting+ bold,+ italic,+ color,+ monospace,+ typeface,+ header,+ height+) where++import FRP.Helm.Color (Color, black)+import FRP.Helm.Graphics (Element(TextElement), Text(..))+import qualified Graphics.Rendering.Cairo as Cairo++{-| Creates the default text. By default the text is black sans-serif+ with a height of 14px. -}+defaultText :: Text+defaultText = Text {+ textUTF8 = "",+ textColor = black,+ fontTypeface = "sans-serif",+ fontSize = 14,+ fontWeight = Cairo.FontWeightNormal,+ fontSlant = Cairo.FontSlantNormal+}++{-| Creates a text from a string. -}+toText :: String -> Text+toText utf8 = defaultText { textUTF8 = utf8 }++{-| Creates a text element from a string. -}+plainText :: String -> Element+plainText utf8 = text $ toText utf8++{-| Creates a text element from any showable type, defaulting to+ the monospace typeface. -}+asText :: Show a => a -> Element+asText val = text $ monospace $ toText $ show val++{-| Creates an element from a text. -}+text :: Text -> Element+text = TextElement++{- TODO:+centered+justified+righted+underline+strikeThrough+overline+-}++{-| Sets the weight of a piece of text to bold. -}+bold :: Text -> Text+bold txt = txt { fontWeight = Cairo.FontWeightBold }++{-| Sets the slant of a piece of text to italic. -}+italic :: Text -> Text+italic txt = txt { fontSlant = Cairo.FontSlantItalic }++{-| Sets the color of a piece of text. -}+color :: Color -> Text -> Text+color col txt = txt { textColor = col }++{-| Sets the typeface of the text to monospace. -}+monospace :: Text -> Text+monospace txt = txt { fontTypeface = "monospace" }++{-| Sets the typeface of the text. Only fonts+ supported by Cairo's toy font API are currently+ supported. -}+typeface :: String -> Text -> Text+typeface face txt = txt { fontTypeface = face }++{-| Sets the size of a text noticeably large. -}+header :: Text -> Text+header = height 32++{-| Sets the size of a piece of text. -}+height :: Double -> Text -> Text+height size txt = txt { fontSize = size }
FRP/Helm/Window.hs view
@@ -5,17 +5,18 @@ ) where import Control.Applicative+import Control.Arrow import FRP.Elerea.Simple import qualified Graphics.UI.SDL as SDL --- |The current dimensions of the window.+{-| The current dimensions of the window. -} dimensions :: SignalGen (Signal (Int, Int))-dimensions = effectful $ (\s -> (SDL.surfaceGetWidth s, SDL.surfaceGetHeight s)) <$> SDL.getVideoSurface+dimensions = effectful $ (SDL.surfaceGetWidth &&& SDL.surfaceGetHeight) <$> SDL.getVideoSurface --- |The current width of the window.+{-| The current width of the window. -} width :: SignalGen (Signal Int) width = effectful $ SDL.surfaceGetWidth <$> SDL.getVideoSurface --- |The current height of the window.+{-| The current height of the window. -} height :: SignalGen (Signal Int) height = effectful $ SDL.surfaceGetHeight <$> SDL.getVideoSurface
README.md view
@@ -3,10 +3,10 @@ ## Introduction Helm is a functionally reactive game engine written in Haskell and built around-the [Elerea](https://github.com/cobbpg/elerea) FRP framework. Helm is+the [Elerea FRP framework](https://github.com/cobbpg/elerea). Helm is heavily inspired by the [Elm programming language](http://elm-lang.org) (especially the API). All rendering is done through a vector-graphics based API. At the core, Helm is-built on SDL and the Cairo vector graphics library. This may change to a more+built on SDL and the Cairo vector graphics library. The plan is to change to a more robust setup in the future, such as a lightweight homebrewed renderer built on OpenGL. But for now, Cairo performs pretty well. @@ -18,45 +18,66 @@ ## Features * Allows you to express game logic dependent on input in a straightforward manner,- treating events as (almost) first class objects (the essence of FRP).+ treating events as first class values (the essence of FRP).+ * Vector graphics based rendering, allow you to either write art designed for any resolution or still load generic images and render those as you would with any pixel-based direct blitting game engine.-* Straightforward API heavily inspired by the Elm programming language. -## Future Work+* Straightforward API heavily inspired by the Elm programming language. The API+ is broken up into the following areas: -* Improve the API. There's a few API calls from Elm that would work- just as nicely in Helm. These are marked inside TODOs in the code.- There also other important things that it's missing,- such as audio, text rendering, joysticks and loading a larger range of- image formats.-* Backend wise, it would be nice to use OpenGL instead of Cairo.- Cairo isn't particuarly that well performing for graphic intensive games,- although work is done being towards to fix that. However, using- OpenGL would make the engine more lightweight, easier to port- and be incredibly easier to accelerate. This means I have- to write the full vector graphics stack myself, but the worse part- will probably just be line styles, the rest should be moderately easy.- This will also allow loading of multiple image formats, as the current- reason for not using SDL_image is that it's annoying as fuck- to integrate with Cairo.-* Optimizations and testing. This is the first release of the engine so- obviously little testing or optimizations have been done.- It's a little hard to set up a test framework for a game engine,- but I have a few ideas, such as writing a dummy version of the backend- that simply renders to a PNG file that is fed fake (but predictable) input,- which is then compared to a static PNG file to see if the final expected- rendering outcome was achieved.-* Port and support multiple platforms. I've only been testing it on- Linux, but there's really no reason that it wouldn't work out of the box- on Windows or OSX after setting up the dependencies. But I'd definitely- also like to investigate Android and iOS.+ * `FRP.Helm` contains the main code for interfacing with the game engine but+ also includes some utility functions and the modules `FRP.Helm.Color` and `FRP.Helm.Graphics`+ in the style of a sort of prelude library, allowing it to be included and readily+ make the most basic of games. + * `FRP.Helm.Automaton` contains the `Automaton` data structure and functions+ for composing, creating and calculating them. Automatons are a useful+ abstraction of a dynamic process that is fed input from a signal+ and feeds output through a signal. This is really useful for things+ like animation systems, accumulating network packets and other+ stateful but input dependent things.++ * `FRP.Helm.Color` contains the `Color` data structure, functions for composing+ colors and a few pre-defined colors that are usually used in games.++ * `FRP.Helm.Graphics` contains all the graphics data structures, functions+ for composing these structures and other general graphical utilities.++ * `FRP.Helm.Joystick` contains signals for working with joystick state.++ * `FRP.Helm.Keyboard` contains signals for working with keyboard state.++ * `FRP.Helm.Mouse` contains signals for working with mouse state.++ * `FRP.Helm.Text` contains functions for composing text, formatting it+ and then turning it into an element.++ * `FRP.Helm.Window` contains signals for working with the game window state.+ ## Example -The following examples is the barebones of a game. It shows how to create-an accumulated state that depends on the values sampled from signals (e.g. mouse input and such).+The simplest example of a Helm game that doesn't require any input from the user is the following:++```haskell+import FRP.Helm+import qualified FRP.Helm.Window as Window++render :: (Int, Int) -> Element+render (w, h) = collage w h [move (100, 100) $ filled red $ square 64]++main :: IO ()+main = run $ do+ dims <- Window.dimensions++ return $ fmap render dims+```++It renders a red square at the position `(100, 100)` with a side length of 64px. + +The next example is the barebones of a game that depends on input. It shows how to create+an accumulated state that depends on the values sampled from signals (e.g. mouse input). You should see a white square on the screen and pressing the arrow keys allows you to move it. ```haskell@@ -71,7 +92,8 @@ data State = State { mx :: Double, my :: Double } step :: (Int, Int) -> State -> State-step (dx, dy) state = state { mx = (realToFrac dx) + mx state, my = (realToFrac dy) + my state }+step (dx, dy) state = state { mx = (realToFrac dx) + mx state,+ my = (realToFrac dy) + my state } render :: (Int, Int) -> State -> Element render (w, h) (State { .. }) = collage w h [move (mx, my) $ filled white $ square 100]@@ -106,3 +128,51 @@ ## License Helm is licensed under the MIT license. See the `LICENSE` file for more details.++## Contributing++Helm would benefit from either of the following contributions:++1. Try out the engine, reporting any issues or suggestions you have.++2. Look through the source, get a feel for the code and then+ contribute some features or fixes. If you plan on contributing+ code please submit a pull request and follow the formatting+ styles set out in the current code: 2 space indents, documentation+ on every top-level function, favouring monad operators over+ do blocks, etc.++The following is a list of areas I want to tackle in the future, +and possible targets that others could try for:++* Improve the API. There's a few API calls from Elm that would work+ just as nicely in Helm. These are marked inside TODOs in the code.+ There also other important things that it's missing,+ such as audio, joysticks and loading a larger range of+ image formats.++* Backend wise, it would be nice to use OpenGL instead of Cairo.+ Cairo isn't particuarly that well performing for graphic intensive games,+ although work is done being towards to fix that. However, using+ OpenGL would make the engine more lightweight, easier to port+ and be incredibly easier to accelerate. This means I have+ to write the full vector graphics stack myself, but the worse part+ will probably just be line styles, the rest should be moderately easy.+ This will also allow loading of multiple image formats, as the current+ reason for not using SDL_image is that it's annoying as fuck+ to integrate with Cairo. Helm also currently uses the Cairo toy text+ API for rendering, which isn't suppose to be used in production. If switched+ to OpenGL, SDL_ttf would be a better fit.++* Optimizations and testing. This is a early release of the engine so+ obviously little testing or optimizations have been done.+ It's a little hard to set up a test framework for a game engine,+ but I have a few ideas, such as writing a dummy version of the backend+ that simply renders to a PNG file that is fed fake (but predictable) input,+ which is then compared to a static PNG file to see if the final expected+ rendering outcome was achieved.++* Port and support multiple platforms. I've only been testing it on+ Linux, but there's really no reason that it wouldn't work out of the box+ on Windows or OSX after setting up the dependencies. But I'd definitely+ also like to investigate Android and iOS.
helm.cabal view
@@ -1,5 +1,5 @@ name: helm-version: 0.2.0+version: 0.3.0 synopsis: A functionally reactive game engine. description: A functionally reactive game engine, with headgear to protect you from the headache of game development provided.@@ -12,7 +12,7 @@ author: Zack Corr maintainer: Zack Corr <zack@z0w0.me> copyright: (c) 2013, Zack Corr-category: Game Engine+category: Game Engine, FRP build-type: Simple cabal-version: >=1.10 @@ -23,10 +23,13 @@ library exposed-modules: FRP.Helm+ FRP.Helm.Automaton FRP.Helm.Color FRP.Helm.Graphics+ FRP.Helm.Joystick FRP.Helm.Keyboard FRP.Helm.Mouse+ FRP.Helm.Text FRP.Helm.Window build-depends: base >= 4 && < 5,