#30daysofelm Day 23: Simple layout with elm-ui
Kristian Pedersen
Posted on January 8, 2021
This is day 23 of my 30 day Elm challenge
Try + view source: https://ellie-app.com/c2WWjMTM4ZLa1
About today's project
I made a React project a few months back that I eventually want to remake in Elm: https://kristianpedersen.github.io/knights-tour-react/
Basically, you click a chess board position, and the program finds a valid sequence of moves where the knight visits every position on the board.
I'm happy with the concept and interactions, but the visuals need a lot of work.
Especially the board being positioned all the way to the left looks weird, and somehow in my caffeine-crazed coding bonanza, I managed to get the SVG lines to resize, but a "fix" stopped the board from resizing. :D
Also, the colors and borders make it look like it was made 15 years ago.
Elm-ui is much nicer to read
I touched upon this in my previous quick elm-ui encounter, but CSS can be difficult.
In my case, struggling with CSS is mostly my own fault for not bothering to learn it properly.
Still though, here's a centered div:
<html>
<head>
<style>
.center-screen {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
min-height: 100vh;
}
</style>
</head>
<body>
<div class="center-screen">
I'm in the center
</div>
</body>
</html>
From https://stackoverflow.com/questions/31217268/center-div-on-the-middle-of-screen
Here's my elm-ui program from day 7:
import Element exposing (..)
centeredDiv =
column
[ width (px 200)
, height (px 200)
, centerX
, centerY
]
[ el [ centerX, centerY ] (text "I am centered")
]
main =
layout [] centeredDiv
There's just a lot less noise in the second example. Now, if this is true in such a small example, imagine how this adds up.
Today's code
I'm still an elm-ui
beginner. I had some problems initially that happened because I provided the wrong type annotations.
Once I removed them, the errors went away too, so today is an automatic type inference day.
Generic slider function
The code for making a slider in elm-ui
is kind of long, to be honest.
My project needed two sliders, but I didn't want two huge slider functions, so I made one generalized slider function:
slider attributes =
Input.slider
[ height (px 30)
, behindContent
(el
[ width fill
, height (px 2)
, centerY
, Background.color (rgb255 255 255 255)
, Border.rounded 2
]
none
)
]
{ onChange = attributes.message
, label =
Input.labelAbove []
(text attributes.text)
, min = attributes.min
, max = attributes.max
, step = Just 1
, value = attributes.value
, thumb =
Input.defaultThumb
}
attributes
is a record that contains the info I would want in a smaller slider function.
The sliders for the board size and animation speed now look nicer, instead of repeating the whole mess above twice:
sliderBoardSize model =
el [ alignLeft ]
(slider
{ min = 5
, max = 26
, value = model.boardSize
, text = "Board size"
, message = UpdateBoardSize
}
)
sliderAnimationRate model =
el [ alignRight ]
(slider
{ min = 5
, max = 1000
, value = model.animationSpeed
, text = "Animation speed"
, message = UpdateAnimationSpeed
}
)
Panel for the controls
The two sliders then get included in a "nav bar", which looks like this:
controls model =
row [ width fill, centerX, spacing 30, padding 30, Background.color (rgb255 200 200 100) ]
[ sliderBoardSize model
, sliderAnimationRate model
]
What's there to say? It's a full-width row with two sliders in it. elm-ui
code is just so nice to look at! <3
Info text
Then underneath the controls, I wanted some info text to be displayed:
infoText model =
row
[ width fill
, centerX
, spacing 30
, padding 30
, Background.color (rgb255 50 50 50)
, Font.color (rgb255 200 200 200)
]
[ el [ centerX ]
(text
("The board size is "
++ (model.boardSize |> Debug.toString)
++ ", and the animation interval is "
++ (model.animationSpeed |> Debug.toString)
++ " ms"
)
)
]
"Board"
Honestly, I just want to watch some Netflix right now, so I didn't bother doing the board properly with the letters and numbers. I'll do that tomorrow.
For now, it expands and contracts along with the board size slider, which I think is pretty cool. :)
showRow model =
row [ width fill, height fill ] <|
(List.range 1 (floor model.boardSize)
|> List.map (\_ -> el [ width fill, centerY ] (text "Hi there"))
)
showBoard model =
column [ width fill, height fill, Font.center ]
(List.range 1 (floor model.boardSize)
|> List.map (\_ -> showRow model)
)
First, we create a row of 8 cells: ********
Then, we create a column with 8 of those:
********
********
********
********
********
********
********
********
The main fullscreen container, and the view function
view model =
Element.layout []
(fullscreenContainer model)
fullscreenContainer model =
column [ width fill, height fill, spacing 0, padding 0 ] <|
List.map
(\elmUiSection -> elmUiSection model)
-- I want to add the "buttons" function to this list, but Elm won't let me because of a type mismatch
[ controls, infoText, showBoard ]
Element.layout
is the topmost level in this elm-ui
application. In my case, it just takes a column
that contains everything.
I'm using map, because each of the elmUiSection
s, ([controls, infoText, showBoard]) take the model as their argument.
I could have also written this:
column [ width fill, height fill, spacing 0, padding 0 ] <|
[ controls model, infoText model, showBoard model ]
But that kind of needless repetition just puts me asleep.
The "buttons" function didn't work in this list. Lists can only consist of items of the same type, and the button is a different type because of its onPress
message.
Main, model, update, the usual stuff
module Main exposing (..)
import Browser
import Element exposing (Element, alignLeft, alignRight, behindContent, centerX, centerY, column, el, fill, height, none, padding, px, rgb255, row, spacing, text, width)
import Element.Background as Background
import Element.Border as Border
import Element.Font as Font
import Element.Input as Input
main : Program () Model Msg
main =
Browser.sandbox
{ init = init
, view = view
, update = update
}
type alias Model =
{ boardSize : Float
, animationSpeed : Float
}
init : Model
init =
{ boardSize = 8, animationSpeed = 50 }
type Msg
= UpdateBoardSize Float
| UpdateAnimationSpeed Float
| BoardSizeFromButton Int
update : Msg -> Model -> Model
update msg model =
case msg of
UpdateBoardSize v ->
{ model | boardSize = v }
UpdateAnimationSpeed v ->
{ model | animationSpeed = v }
BoardSizeFromButton v ->
{ model | boardSize = toFloat v }
This is a very standard Elm setup. I'd definitely change the Msg names, as they're not really clear. SliderBoardSize
, SliderAnimationSpeed
and ButtonBoardSize
would be better names for me.
Also, almost at the top, rather than exposing so many individual things from Element, I think I would just expose all.
Conclusion
This was my 2nd time using elm-ui
. Type annotations gave me some trouble to begin with, but other than that, I must say that it feels really intuitive.
I'm just going to be honest and say that it would have taken me a lot more effort to make today's project in CSS.
My CSS skills aren't good, so I use elm-ui
, and my React code gives me runtime errors, so I use elm
.
I'll definitely look more into elm-ui for future projects - it's really nice.
Posted on January 8, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 28, 2024