Well, probably not what you were wishing for when you thought about
what you wanted for 2011, but I implemented it anyway.
What is a length literal? Here are a few examples:
12px
5mm
2cm
1.5em
They are similar to the Duration type, but applicable to a lot more
places (think of how many different places you specify length or
pixel values in most APIs).
So what other interesting things can you do with them? You can:
add lengths: 5cm + 5mm
subtract lengths: 5cm - 5mm
divide lengths: 5mm / 2
divide lengths by lengths: 5cm / 5mm
compare lengths: 5mm < 5cm
This is all pretty basic, but the complexity comes in with different
types of length. How do you convert from pixels to inches? What
length is an em? I looked at all the different length systems in
common use today, and came up with what I think is a reasonable
system. Feel free to weigh in...
The Visage Length class supports 4 different classes of lengths:
- pixel - Exact screen pixels. This should not be used for
layouts that need to be resizable or device portable.
- dp - Density-independent pixels - Reference pixel for the
target device. Will scale to a whole or even fractional pixel
value based on the device viewing distance and density. This is
approximately one pixel on a device with a density of 96dpi at
arm's length. For example:
- Desktop - 28 inches away, .26mm pixel (96 dpi), 1dp = 1px
- Medium Density Mobile - 17 inches away, .16mm pixel (160
dpi), 1dp = 1px
- High Density Mobile - 17 inches away, .16mm pixel (240 dpi),
1dp = 1.5px
- 55" HD TV - 10 feet away, .73mm pixel (35 dpi), 1dp = 1.5px
- sp - Scale-independent pixels - Similar to dp, but also
relative to the user font scale. For the default font scale
will be 1 to 1 with dp.
- em - Typographic measure relative to the enclosing element's
font height.
- percentage - Percentage of the enclosing element's length or
size.
The DP and SP units are inspired from the Android APIs. DP is
actually equivalent to the reference pixel concept in CSS, so it is
really nothing new. However, defining it as 160dpi makes a lot of
sense (for mobile), just not for desktop.
SP units also have a CSS equivalent. They are semantically similar
to the new rem (root em) unit defined in CSS3. It solves a
fundamental problem with the em unit, which is the context
sensitivity. It is often difficult to figure out what size an em
will show up as. Also, simply reparenting an element specified in
ems may change its size.
Inches, centimeters, millimeters, points, and picas are losslessly
converted into density-independent pixels at a scale of 96dpi
(preserving compatibility with CSS). This is a pretty good
approximation of print-format needs, so it should prevent gross
resizing of pixels when inches become fixed to a physical unit.
Percentage is a special case... it is entirely context dependent
(on the container and field usage), so there is really no point
converting it.
To make it possible to include these concepts in the language where
there is no knowledge of the device metrics or scenegraph context, I
introduced a concept of ComplexLengths. Addition and subtraction of
simple lengths may result in a new length object that cannot be
simplified, such as:
println("complex = {5px + 5dp}");
This will literally print the following:
complex = 5px + 5dp
However, if an expression can be simplified, it will be:
println("simple = {%#s 1cm + 1mm}
simple = 11mm
Notice that I used a funky formatting symbol on that expression.
The Length type also implements Formattable with full support for
width, precision, capitalization, justification, and alternate
formats. The alternate format for lengths is to choose an
equivalent unit that has the shortest form (in this case mm). The
normal format method will simply use the base type (in this case dp)
and print a decimal value to 6 significant digits.
When lengths are complex or simply incompatible, certain operations
may not work. For example, you can't compare px and dp, so this
will throw an IncompatibleLengthException:
5px < 5dp
You could fix this by first converting them to compatible units like
the following:
def dps = 5px.toDensityIndependentLength(1.5);
5px < dps
Notice that I passed in a density factor of 1.5. This means 1px =
1.5dp, which would be appropriate for a very high resolution display
(like a 240dpi WVGA phone display).
There are similar conversion methods for every combination that I
thought makes sense... although in practice, the one that will
prove the most useful is the toPixels method.
So with all this unit madness, what do I expect people to really do?
- API Designers: Use the Visage Length type anywhere a length or
font size would normally be used. When you get back a length,
simply convert it to pixels (passing in the appropriate
conversion factors) and let the Visage implementation do all the
complex math.
- Application Developers: Use lengths that make sense for the
context you are working in... DPs (or a compatible measure)
most of the time. EMs if you want to line up to the enclosing
text. SPs when you want something to scale with the user font
preference. Pixels when you really need to make sure it lines
up with the device pixel grid (thin lines, etc.) Mixing and
matching lengths is fine too.
And why should this be a feature of Visage, and not the APIs built
on top (maybe I should have started with this):
- Succinctness - It is really nice to simply be able to write
1mm rather than Length {value: 1, unit: LengthUnit.MM}
- Uniformity - CSS does lengths one way, Android another, JavaFX
another... and even in the best case you have platform
differences (Mac/Windows, IE/Webkit) that further complicate
things. As long as you let Visage do the conversion of lengths,
you can (mostly) ignore the underlying funky toolkits.
The code for all this Length literal goodness is checked in if you
want to take a look. Here is a link to the change list (which also
includes a first pass at an Angle literal... more on this soon):
http://code.google.com/p/visage/source/detail?r=00179720e5af01bd021ff2f46ed66b5312ae966d
Cheers,