Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support custom line thickness #31

Closed
joeyadams opened this issue Oct 4, 2012 · 9 comments
Closed

Support custom line thickness #31

joeyadams opened this issue Oct 4, 2012 · 9 comments
Assignees
Labels

Comments

@joeyadams
Copy link
Contributor

Elm currently does not let you set the thickness of a line. Actually, you can make thick lines using scale, but you have to tweak your coordinates.

I suppose the API could be this:

thickness :: Number -> Form -> Form

This would set the thickness of lines, both for polygon edges (coming from Shape) and line paths (coming from Line).

While we're at it, it would be nice if line and segment returned a Form instead of a Line. It's annoying to have to specify the style and color of a line just to get it on the page:

main = collage 500 500
   [ solid black $ segment (0,0) (100, 100)
   ]

I'd rather just say:

main = collage 500 500
   [ segment (0,0) (100, 100)
   ]

and have it default to a solid black line.

I would tackle this, but Element.js looks like it came from a code generator. Do you have the original Elm source, or do you just tweak the generated code now?

@evancz
Copy link
Member

evancz commented Oct 5, 2012

It is generated code, but I edit it directly to add imperative stuff as necessary.

I am happy to make the Line -> Form functions more robust. I am thinking of a super general one that lets you specify thickness, pattern, color, and how the line ends (rounded or squared). That could be used to define pretty much any kind of Line -> Form function (or points -> Form functions) you want.

I have reservations about your suggestions though. I do not like the idea that thickness can be applied to any Form. What does it mean to set the thickness of a sprite? Or an Element that has a border?

The second suggestion seems to hinge on functions like dotted :: Color -> Form -> Form which has the same problem as thickness. I`d rather avoid this kind of ambiguity.

Here's my long term plan for forms, shapes, and lines. I'd like to have functions like:

intersection :: Shape -> Shape -> Shape
union :: Shape -> Shape -> Shape
setTextOn :: Line -> Text -> Form

And once there are typeclasses, I'd like to make move, scale, rotate, isWithin, etc work on lines and shapes as well as on forms. Maybe this is less useful than I think, but I'd rather wait to see than give it up now.

To get the default behavior you are describing, maybe there could be functions like:

segmentSB :: Point -> Point -> Form
lineSB :: [Point] -> Form

Where SB is "solid black". Or maybe sbSegment and sbLine. In any case, I do not want to give up on the Line abstraction, at least not in the core library.

@joeyadams
Copy link
Contributor Author

Thanks, you make very good points.

I'd still like to see a styling system that can be extended without breakage. Another idea might be:

data LineStyle -- abstract
lineColor :: Color -> LineStyle
solid, dashed, dotted :: LineStyle
customLineStyle :: [Number] -> Color -> LineStyle

line :: [LineStyle] -> Line -> Form

-- Renamed from 'line'
path :: [(Number,Number)] -> Line

Thus, line [] (segment (0, 0) (0, 0)) uses the default style (solid), color (black), and thickness (1.0).

@evancz
Copy link
Member

evancz commented Oct 5, 2012

I like this approach more. It is similar to how I ended up changing the HTTP library (old, new). Also, I really really like the name path; I've been trying to think of something more mathematically correct than line for a while.

There are still some ambiguities that can be avoided though. For example:

line [solid, dashed, dotted] (segment p1 p2)

I'll try an API now, keeping an eye on these docs so that it is as general as possible. This first part is about filling shapes and lines:

filled :: FillStyle fs =>  fs -> Shape -> Form
solid, dotted, dashed :: FillStyle fs => fs -> Line -> Form

instance FillStyle Color
instance FillStyle Gradient
instance FillStyle Texture

linearGradient :: Point -> Point -> [(Float,Color)] -> Gradient
radialGradient :: Point -> Float -> Point -> Float -> [(Float,Color)] -> Gradient
texture :: String -> Texture

This next part is about lines:

outlined :: StrokeStyle ss => ss -> Shape -> Form
line :: StrokeStyle ss => ss -> Line -> Form

instance StrokeStyle LineStyle
instance StrokeStyle Color
instance StrokeStyle LinePattern

customLineStyle :: FillStyle s => LineCap -> LineJoin -> Number -> LinePattern -> s -> LineStyle
lineStyle :: FillStyle s => Number -> LinePattern -> s -> LineStyle

buttCap, roundCap, squareCap :: LineCap

miterJoin, bevelJoin, roundJoin :: LineJoin
miterJoin' :: Number -> LineJoin

solidPattern, dashedPattern, dottedPattern :: LinePattern
customPattern :: [Number] -> LinePattern

I believe that none of this should cause any breaking changes.

As a side note, this seems like a fun function:

position :: Line -> Float -> (Number,Number)

Which would return the position that is some percentage along the given path. This might make it easier to move forms around in a nice way.

@evancz
Copy link
Member

evancz commented Oct 5, 2012

Maybe line could be named traced.

Also, this is much more complicated than I anticipated!

@evancz
Copy link
Member

evancz commented Oct 5, 2012

I am also really tempted to totally rename type Line to Path. I like it much more, but I am not sure if the Path vs FilePath ambiguity would be annoying.

@evancz
Copy link
Member

evancz commented Oct 5, 2012

Also, maybe rename LinePattern to LineStroke or Stroke.

@joeyadams
Copy link
Contributor Author

There are still some ambiguities that can be avoided though. For example:

line [solid, dashed, dotted] (segment p1 p2)

My two cents: don't worry about it! You have the same situation in imperative code:

ctx.strokeStyle = 'black';
ctx.strokeStyle = 'blue';

in JavaScript object notation:

var obj = {a: 'black', a: 'blue'};

and even in Haskell's Data.Map:

Map.fromList [("a", "black"), ("a", "blue")]

In all of these cases, "blue" overrides "black" because it is listed last. So for:

line [solid, dashed, dotted] (segment p1 p2)

Just make dotted shadow the conflicting styles, as it is listed last.

@evancz
Copy link
Member

evancz commented Oct 8, 2012

Sorry for the delay, I have been without internet for a bit. I see the convenience of it, but I think the larger issue is "how do you do optional arguments in a functional language with type inference?" and as far as I know, there is not a nice way to do it.

I think maps and dictionaries are not comparable because that is a problem where types cannot rule out ambiguities and conflicts. An association list can have duplicate keys and (short of dependent types) that's just the way it is. So fromList accommodates this. We are not in that situation though. For us, it is possible to rule out ambiguities and conflicts at the type level and I think that is valuable.

That said, your proposal appears to be simple and nice. I am not sure if it would end up being simpler once it is fully fleshed out, but I suspect it might. My biggest hesitation is that I have never seen an API like this in a functional language in a comparable situation (i.e. when types could be used to resolve ambiguities). Do you know of any libraries or languages that expose APIs like the one you suggest? If not, why should Elm be the first language to do so?

@ghost ghost assigned evancz Oct 25, 2012
@evancz
Copy link
Member

evancz commented Aug 15, 2013

This got added! It's definitely in 0.8 and 0.9 :)

@evancz evancz closed this as completed Aug 15, 2013
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants