How to convert Element into Form? - elm

How can I convert Mouse.position into Form, so I can display it in a collage? The following code displays <Signal> instead of the actual mouse coordinates:
render (x, y) =
let mousePos = toForm (show Mouse.position)
in collage 400 400 [mousePos]
It's curious that in this example http://elm-lang.org/examples/mouse-position, the show function actually transforms Mouse.position into a string with coordinates, but that is because the show function is used to filter a Signal(Int, Int) into a tuple of Signal values.
So my question is, how do I convert a Signal(Int, Int) into a Form, so that it shows the tuple values?

You are looking for Graphics.Collage.toForm which has the type Element -> Form.
It also sounds like you don't quite understand what Signal.map is doing. It takes a function to be applied to each value of a Signal. I've tried to use it in several contexts in the following example.
import Graphics.Element exposing (..)
import Graphics.Collage
import Graphics.Collage exposing (Form)
import Mouse
--This is the function you are trying to construct.
--It takes in a position, converts it to an element,
--using show and then converts it to a Form.
formPosition : (Int, Int) -> Form
formPosition pos =
let element = show pos -- element : Element
in Graphics.Collage.toForm element
-- We now want to apply our formPosition function to the
-- Signal containing all mouse position changes.
-- So we use Signal.map to apply formPosition to all values
-- of Mouse.position
formSignal : Signal Form
formSignal = Signal.map formPosition Mouse.position
-- Eventually we want to render this on the screen and the
-- function to do this requires a List Form not just a single
-- Form. So we write a function which returns a Singleton list
-- and apply it to each value in our formSignal.
formListSignal : Signal (List Form)
formListSignal = Signal.map (\n -> [n]) formSignal
-- Finally, we must turn that into a Signal Element to render
-- on the screen. We partially apply Graphics.Collage.collage
-- to return an element of size 400x400 and apply it to the
-- values of formListSignal by using Signal.map again
elementSignal : Signal Element
elementSignal = Signal.map (Graphics.Collage.collage 400 400) formListSignal
-- Finally we hand this off to main and it renders
main : Signal Element
main = elementSignal
A simpler version would likely combine all of the conversions into a single function. I just wanted to emphasize how Signal.map worked. I hope this helps!

Related

How to update "variables" in main with IO and GTK [duplicate]

Trying to learn to write applications with Gtk2Hs I'm getting difficulties bridging the gap between the event driven Gtk2HS and the persistent state of my model. So to simplify, lets say that I have this simple application
module Main where
import Graphics.UI.Gtk
import Control.Monad.State
main = do
initGUI
window <- windowNew
button <- buttonNew
set button [buttonLabel := "Press me"]
containerAdd window button
-- Events
onDestroy window mainQuit
onClicked button (putStrLn ---PUT MEANINGFUL CODE HERE---)
widgetShowAll window
mainGUI
and the state of my application is how many times the button has been pressed. Seeing other posts like this they rely on MVars or IORefs which do not seem satisfactory to me, because in the future maybe I will want to refactor the code so the state lives on its own context.
I think that the solution should use the State monad using a step function like:
State $ \s -> ((),s+1)
but I'm not sure about the implications, how to do that in the above code or even if that monad is the right solution to my problem.
There's basically two approaches:
Use a pointer of some kind. This is your IORef or MVar approach. You can hide this behind a MonadState-like interface if you like:
newtype GtkT s m a = GtkT { unGtkT :: ReaderT (IORef s) m a } deriving (Functor, Applicative, Monad, MonadIO)
runGtkT = runReaderT . unGtkT
instance MonadIO m => MonadState s (GtkT s m) where
get = GtkT (ask >>= liftIO . readIORef)
put s = GtkT (ask >>= liftIO . flip writeIORef s)
Pull an "inversion of control" style trick. Write a callback that prints a number, then replaces itself with a new callback that prints a higher number.
If you try to use State or StateT directly, you're gonna have a bad time.

how to merge two signals with SampleOn in Elm

I try to merge two signals. One is Mouse.clicks and another is Keyboard.space.
On clicks, I should get a Signal(Int,Int) from Mouse.position as return value
On space, I should get something different so I can identify different signal is triggered.
My idea is:
type Event = Click | Space
mergedSignal : Signal Event
mergedSignal =
let
clickSignal = map (\event -> Click) Mouse.clicks
timeoutSignal = map (\event -> Space) Keyboard.space
in
merge clickSignal timeoutSignal
and get position somehow:
positionOnClickSignal:Signal (Int,Int)
positionOnClickSignal = sampleOn Mouse.clicks Mouse.position
Obviously, it is wrong.
It sounds like you want the mouse position to carry over as part of the event. In that case, you could redefine Event as
type Event
= Click (Int, Int)
| Space
Inside your mergedSignal, the clickSignal is currently just checking for Mouse.clicks but based on your description and other example, I think you actually want that to be based off positionOnclickSignal, which gives you a Signal (Int, Int), and using that, you can now populate the (Int, Int) portion of the Click (Int, Int) event like this:
clickSignal =
map Click positionOnClickSignal
You'll notice that I took out the parenthesis in the above. That is more idiomatic for Elm, because Click is in essence a function that takes one (Int, Int) parameter, which will be passed in from the map function. It could have easily been written like this:
clickSignal =
map (\pos -> Click pos) positionOnClickSignal
Now, if you're just trying to see some debug text of this on screen, a quick and easy way to go about that is to use show from the Graphics.Element package.
import Graphics.Element exposing (show)
main =
map (show << toString) mergedSignal
That will give you some debug text shown as the only thing on the page, and you could easily toss it up on http://elm-lang.org/try for testing.

Cannot read property 'kids' of undefined - or how to break a circular dependency of signals in Elm?

While elm-make succeeds, I get the following error in the browser:
Cannot read property 'kids' of undefined
I assume it's because I have a circular dependency of signals:
model -> clicks -> model
Here is the relevant code:
model : Signal Model
model =
Signal.foldp update initialModel clicks
clicks : Signal Action
clicks =
let
clickPosition = Mouse.position
|> Signal.sampleOn Mouse.clicks
tuplesSignal = Signal.map3 (,,) clickPosition Window.dimensions model
in
...
It feels like model is implemented as a common practice in Elm, so I should challenge the clicks -> model dependency.
Here is some context:
I'm building a sliding puzzle game using canvas:
When user clicks a tile that can move, it should move. Otherwise, the click should be ignored.
clicks will produce the following actions: Left, Right, Up, Down
For example, if user clicks on tiles 12, 11, 8, 15 (in this order), clicks should be: Down -> Right -> Up
The problem is, that in order to calculate which tile was clicked, I need to know the board dimensions (e.g. 4 rows and 4 columns in the picture above). But, board dimensions are stored in the model (imagine user interface that allows users to change board dimensions).
How do I get out of this circular dependency?
In this case I think you should go more low-level in what you call an input, and accept click positions as inputs. Then you can compose an update function in your Signal.foldp out of two others:
The first turns the clicks and model into a Maybe Direction (assuming the Left, Right, Up, Down are constructors of type Direction)
The second function that takes a Direction value and the model to calculate the new model. You can use Maybe.map and Maybe.withDefault to handle the Maybe part of the result of the first function.
Separating these two parts, even though you produce and consume Direction immediately, makes your system more self-documenting and shows the conceptual split between the raw input and the restricted "actual" inputs.
P.S. There are conceivable extensions to the Elm language that would allow you to more easily write this input preprocessing in signal-land. But such extensions would make the signal part of the language so much more powerful that it's unclear if it would make "the right way" to structure programs harder to discover.

How to make collage (with lots of containers) more responsive in Elm?

I am writing a roguelike in Elm, where there is a discrete 50x50 grid (see share-elm.com snippet). A roguelike is a video game, where objects (like enemies, items, walls, etc) are represented by ASCII characters. Therefore I should be able to have hundreds of different ASCII characters, aligned in a rectangular grid. Every character should be strictly within its grid cell.
To create this grid, I put every character in a square container (1/50 size of the actual game container). This means I can have 2500 containers in the game maximum. Elm creates <div> elements for containers, even if I convert these containers to Form and put them inside a collage. This makes my Firefox 39.0 very slow in performance.
How do I create a rectangular grid with nicely aligned ASCII characters (and possibly some other graphical elements) within its grid cells, so that no matter how many elements I have at the same time, the collage still stays quick and responsive? And what is the general idiomatic approach every time I'm writing a program with lots of containers and other elements inside a collage? Or maybe there is a completely different approach to creating snappy rectangular grids in Elm?
One possibility (if you don't mind writing some HTML instead of using collage/container) would be to use the Html.Lazy module. You could, for example, wrap the rendering of each "row" of the display in a lazy and it would only re-render the rows that changed (which should only be 1-2 per timestep/movement).
What you're looking for here is Graphics.Collage.text. When you turn an Element into a Form Elm will take the general approach that can place any Element like a Form, but it doesn't actually draw it on the canvas. (Yay, implementation details). If you instead go straight from Text to Form, it's statically known that it's text, so the faster method of drawing text on a canvas can be used. This is a simple change:
view : (Int, Int) -> Element
view (w,h) =
let
s = min w h -- collageSize
forms = List.map (\(x,y) -> move (s,s) (x,y) playerForm)
<| cartesian 0 (screenSize-1) 0 (screenSize-1)
playerForm = "#"
|> Text.fromString
|> Text.height ((toFloat s) / screenSize)
|> C.text
-- |> E.centered
-- |> E.container (s//screenSize) (s//screenSize) E.middle
-- |> C.toForm
in
E.color Color.lightGray
<| E.container w h E.middle
<| E.color Color.white
<| C.collage s s forms
Instead of the three lines in comments, it's just the C.text. You can see the responsiveness in the updated share-elm snippet.
Note that you can no longer select the text! But otherwise it should be much better.

flip arguments to Elm function call

I am trying to modify the Elm example that shows a single spacer so that it renders multiple spacers of different colors:
import Color exposing (red, blue, yellow)
import Graphics.Element exposing (Element, color, spacer, flow, right)
colors = [ yellow, red, blue ]
presentColors : List Element
presentColors = List.map (color ??? (spacer 30 30)) colors
main : Element
main =
flow right presentColors
However as you can see the function color takes the color argument first and so I cannot create a partially applied version of it for List.map to use.
So how can I flip the arguments to color so that it can be partially applied?
As of Elm 0.19, flip is no longer included by default. The docs recommend named helper functions instead.
Go to the Elm (pre v0.19) libraries page. Press Standard Libraries. In the search box, type in flip and click the function that comes up. That'll give you the documentation for
flip : (a -> b -> c) -> b -> a -> c
Flip the order of the first two arguments to a function.
With which you can do
flip color (spacer 30 30)
which is the same thing as
\c -> color c (spacer 30 30)
Flip was removed from elm/core in 0.19. You could try:
pilatch/flip package instead.