Skip to content

haskell-game/webcolor-labels

Repository files navigation

webcolor-labels

Zero-dependency, plug-and-play library that enables #hex-color syntax for your own types!

Demo image

Motivation

Unrestricted OverloadedLabels syntax was implemented in GHC 9.6.1. It isn't hard to notice that this syntax is a strict superset of hexadecimal CSS color syntax, a.k.a. Web colors. It would be great if GUI libraries could take advantage of this fact and allow their users to write #f00 for the color "red," or even #red, right?

That's where webcolor-labels comes into play. This library implements type-level string parsing and validation and provides you an easy-to-use interface for defining an IsLabel instance. In fact, it's as easy as counting one, two, three:

-- one
import WebColor.Labels
import GHC.OverloadedLabels

-- two
instance IsWebColorAlpha s => IsLabel s YourColor where
  fromLabel = webColorAlpha @s yourColorFromWord8

-- three
yourColorFromWord8 :: Word8 -> Word8 -> Word8 -> Word8 -> YourColor
yourColorFromWord8 red green blue alpha = ...

And that's all!

Syntax

Allowed colors aim to follow the Wikipedia Web Colors page; here is a quick recap:

A color is written as a hex triplet, which is a six-digit (e.g., #fa12c7) or eight-digit (e.g., #fa12c7aa) hexadecimal number. The bytes represent the red, green, blue, and optional alpha channels of the color; hence we have #rrggbbaa.

It is possible to use the shorthand form with three and four digits: #f8c = #ff88cc and #f8c3 = #ff88cc33.

The syntax also supports 16 basic colors for convenience:

Color name Hex value
#white #FFFFFF
#silver #C0C0C0
#gray #808080
#black #000000
#red #FF0000
#maroon #800000
#yellow #FFFF00
#olive #808000
#lime #00FF00
#green #008000
#aqua #00FFFF
#teal #008080
#blue #0000FF
#navy #000080
#fuchsia #FF00FF
#purple #800080

Hex triplet form is case-insensitive; therefore, #fff is the same as #FFF, but basic colors are case-sensitive. That means #red is the same as #f00, but #RED and #Red result in a compile-time error.

FAQ

I want to use this syntax with $LIBRARYNAME, what should I do?

webcolor-label's primary users are other library authors; therefore, you should go to the $LIBRARYNAME's issue tracker and tell them that webcolor-labels will improve the lives of their users.

Alternatively, you may write an orphan instance, but it's a bad idea in general and you should avoid that as much as possible.

generic-lens uses the same syntax. Does that mean it will conflict with an instance defined using webcolor-labels?

No, unless you define a highly polymorphic IsLabel instance or your color is a type alias for a function.

The generic-lens instance applies only if a function is expected in place of #label. Therefore, define instances with a concrete head, and everything will work smoothly.

But what about instances for the color types from different libraries? Might they conflict?

No, if each instance is defined correctly.

I have a type class to represent colors. How can I use # syntax with a function that accepts my type class?

Unfortunately, my library just doesn't fit this use case. It's the same problem as show . read; GHC just can't infer a type in the middle.

Contact info and acknowledgements

If you have any questions, you can email me using [email protected]. Alternatively, you can DM me on Matrix (@root:sandwitch.dev) or Telegram (@sand_witch).

Many thanks to the Russian Haskell gamedev community, who encouraged me to convert this code into a real library and assisted with shaping the API.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published