| Safe Haskell | None |
|---|---|
| Language | Haskell2010 |
Rainbox.Tutorial
Description
The Rainbox tutorial
Rainbox helps you create arrangements of (possibly) colorful text boxes. This module contains a tutorial. Typically the Rainbox module contains all you need. There is also a Rainbox.Core module which contains all the innards of Rainbox, but ordinarily you won't need it.
The basic building block of Rainbox is known as a core. A core is
either a single Chunk or a blank box of arbitrary size. A core made
of a single Chunk should not contain any newlines. Leave newline
handling up to Rainbox. However, Rainbox will not check to ensure
that your Chunk does not contain any newline characters. If it does
contain newlines, your boxes will not look right. Also, Rainbox needs
to know how wide your Chunk are, in columns. To measure width,
Rainbox simply counts the number of characters in the Chunk.
Therefore, if you need accented characters, be sure to use a single
character, not composed characters. That is, to get á, use U+00E1,
not U+00B4 and U+0061.
Many things in Rainbox have height and width. Both the height and
width of an object can be zero, but never less than zero. A core
made from a Chunk always has a height of 1, and its width is equal
to the number of characters in the Texts that make up the Chunk.
A core made from a blank box has the height and width that you give
it, though neither its height nor its width is ever smaller than zero.
The next biggest building block after the core is payload. There
are two different types of payloads: vertical payloads and horizontal
ones. A vertical payload aligns itself next to other vertical
payloads on a vertical axis, creating a chain of payloads. The
vertical payload also has an alignment. The alignment determines
whether the payload lines up along the axis on its left side, right
side, or in the center of the payload.
The vertical payload also has a background color, which has type
Radiant. Think of the background color as extending infinitely from
both the left and right side of the vertical payload. When the
vertical payload is combined with other vertical payloads into a Box
Vertical, this background color is used as necessary so that the
Box Vertical forms a rectangle.
The horizontal payload is similar to the vertical payload, but the axis is horizontal rather than vertical. The alignment determines whether the payload aligns the axis along the top, center, or bottom of the payload. A horizontal payload also contains a background color; it extends infinitely from both the top and bottom of the horizontal payload.
Finally, the biggest building block of Rainbox is the box. There are
two types of boxes: a Box Horizontal, which holds zero or more horizontal
payloads, and a Box Vertical, which holds zero or more vertical payloads.
Each kind of box is a Monoid, so you can combine it using the usual
monoid functions. So, to give a visual, a Box Vertical with five payloads
might look like this:
-- function: box1
V Vertical axis
+----+
| v1 |
| |
+----+--------+
| v2 |
| |
+--------+-------------+
| |
| v3 |
| |
| |
+--------+----------+
| v4 |
+----+----+-----+
| v5 |
| |
+---------+
Each payload is marked in the middle with v1, v2, etc. Note how
each payload has a different size. v1 and v2, and v4 have a
left alignment, as their left edge is lined up with the vertical
axis. v3 has a right alignment. v5 has a center alignment.
Think of each payload has having a background color extending
infinitely off of its left and right sides. These five payloads put
together make a Box Vertical. Since Box Vertical is a monoid,
you can combine various Box Vertical. Indeed, the pictured Box
Vertical can be built only by combining smaller Box Vertical, as
when you create payloads they are always given to you as a single
payload wrapped in a Box Vertical.
Now, you want to render your Box Vertical. You use the render function,
which makes a sequence of Rainbow Chunk. This turns your Box Vertical
into a nice rectangle for on-screen rendering:
-- function: renderBox1
+--------+----+--------+
| | v1 | |
| | | |
+--------+----+--------+
| | v2 |
| | |
+--------+-------------+
| | |
| v3 | |
| | |
| | |
+--------+----------+--+
| | v4 | |
+---+----+----+-----+--+
| | v5 | |
| | | |
+---+---------+--------+
The spaces to the left and right of each payload are filled in with the appropriate background color, which is the background color of the adjoining payload.
What if you want to place the Box Vertical alongside another box? If you
want to put it next to another Box Vertical, just use mappend or <>. But
what if you want to put it next to a Box Horizontal? Let's suppose you have a
Box Horizontal that looks like this:
-- function: box2
+----+
| h1 |
| |
Horizontal Axis > +----+----------+
| |
| h2 |
| |
+----------+
The h1 payload has alignment bottom, because its bottom edge is
lined up along the horizontal axis. The h2 payload has alignment
top. You want to connect this Box Horizontal with the Box
Vertical made above. You can't connect them directly because they
are different types. You can, however, take a complete Box and wrap
it inside another Box. This allows you to wrap a Box Vertical
inside of a Box Horizontal, and vice versa, or even wrap a Box
Horizontal inside of another Box Horizontal. You do this with
the wrap function, which is applied to the alignment for the new
box, its background color, and the box you want to wrap. ao, let's
say you take the Box Vertical created above and wrap it inside a
Box Horizontal with top alignment and a background color, and
then you combine it with the Box Horizontal created above. The
result:
-- function: box3
+----+
| h1 |
| |
+--------+----+--------+----+----------+
| | v1 | | | |
| | | | | h2 |
+--------+----+--------+ | |
| | v2 | +----------+
| | |
+--------+-------------+
| | |
| v3 | |
| | |
| | |
+--------+----------+--+
| | v4 | |
+---+----+----+-----+--+
| | v5 | |
| | | |
+---+---------+--------+
The old Box Vertical, which is now wrapped in a Box
Horizontal, now has a background color which extends infinitely from
the top and bottom of the box. It is now just a payload inside of the
Box Horizontal. The other two payloads in the Box Horizontal,
h1 and h2, also have background colors extending from their tops
and bottoms.
So, when you render this Box Horizontal with render, you get this:
-- function: renderBox3
+----------------------+----+----------+
| | h1 | |
| | | |
+--------+----+--------+----+----------+
| | v1 | | | |
| | | | | h2 |
+--------+----+--------+ | |
| | v2 | +----------+
| | | | |
+--------+-------------+ | |
| | | | |
| v3 | | | |
| | | | |
| | | | |
+--------+----------+--+ | |
| | v4 | | | |
+---+----+----+-----+--+ | |
| | v5 | | | |
| | | | | |
+---+---------+--------+----+----------+
The area above the old Box Vertical has the background color that we used in
the application of convert. The area below the h1 payload has its
background color, and the area above and below the h2 payload has
its background color.
What if you just want to create an empty space? You can create entire
blank boxes with blank, but often it is enough to use spacer,
which gives you a one-dimensional Box Horizontal or Box
Vertical. If you are creating a Box Horizontal, spacer gives
you a box with width, but no height; for a Box Vertical, you get a
box with height, but no width. So, to return to the example Box
Horizontal, let's say you want to add a blank space a few columns
wide on the right side. You mappend a Box Horizontal created
with spacer to get this:
-- function: box4
+----+
| h1 |
| |
Horizontal Axis > +----+----------+--+
| |
| h2 |
| |
+----------+
On the right side you now have a payload with width, but no height.
But it does have a background color. So when you render the box, you
get this:
-- function: renderBox4
+----+----------+--+
| h1 | | |
| | | |
+----+----------+ |
| | | |
| | h2 | |
| | | |
+----+----------+--+
You can also use spreader to make a Box Horizontal taller or a
Box Vertical wider. spreader creates a one-dimensional Box
that is perpendicular to the axis. Pay attention to the alignment of
the spreader as this will determine how your box expands. Let's say
I want to take the Box that contains the h1 and h2 payloads as
created above, but I also want to make sure the box is at least 12
rows tall. To do this, mappend a spreader that is 12 rows tall.
The result upon rendering is:
-- functions:box5renderBox5+----+----------+--+ | | | | +----+ | | | h1 | | | | | | | +----+----------+ | | | | | | | h2 | | | | | | | +----------+ | | | | | +----+----------+--+
Those are the basics of the Rainbox model, which should be enough to
get you started. Also helpful are the tableByRows and
tableByColumns functions, which will help you build a simple grid
that resembles a spreadsheet; see its documentation for hints to get
started with that. You will also find an example using the
tableByRows, as well as code to produce all of the examples shown
above, in the source code below.
Why the use of Seq everywhere, rather than lists?
Rainbox uses Seq from Data.Sequence because lists can be
infinite. Practically every function in Rainbox will not accept
infinite inputs, because Rainbox needs to know exactly how long and
wide various payloads and boxes are in order to line them up
correctly. Use of the Seq most accurately reflects the fact that
Rainbox does not work on infinite inputs.
Synopsis
- textBox :: Radiant -> Text -> Box a
- within :: Orientation a => Alignment a -> Int -> Int -> Radiant -> Box a -> Box a
- textWithin :: Orientation a => Alignment a -> Int -> Int -> Radiant -> Radiant -> Text -> Box a
- box1 :: Box Vertical
- renderBox1 :: IO ()
- box2 :: Box Horizontal
- renderBox2 :: IO ()
- box3 :: Box Horizontal
- renderBox3 :: IO ()
- box4 :: Box Horizontal
- renderBox4 :: IO ()
- box5 :: Box Horizontal
- renderBox5 :: IO ()
- data Line
- data Station = Station {
- name :: Text
- metroLines :: [Line]
- address :: [Text]
- underground :: Bool
- nameCell :: Radiant -> Text -> Cell
- linesCell :: Radiant -> [Line] -> Cell
- lineRow :: Radiant -> Line -> Seq Chunk
- addressCell :: Radiant -> [Text] -> Cell
- undergroundCell :: Radiant -> Bool -> Cell
- stations :: [Station]
- coloredBack :: Radiant
- stationRow :: Radiant -> Station -> [Cell]
- allStationRows :: [[Cell]]
- verticalStationTable :: Box Vertical
- renderVerticalStationTable :: IO ()
- stationColumn :: Station -> [Cell]
- allStationColumns :: [[Cell]]
- horizontalStationTable :: Box Horizontal
- renderHorizontalStationTable :: IO ()
Documentation
textBox :: Radiant -> Text -> Box a Source #
Create a Box for the given text. The default foreground and
background colors of the terminal are used for the Text; the
given background is used as the background color for any added
padding.
Arguments
| :: Orientation a | |
| => Alignment a | |
| -> Int | Number of rows |
| -> Int | Number of columns |
| -> Radiant | Background color |
| -> Box a | |
| -> Box a |
Centers the given Box within a larger Box that has the given
height and width and background color. The larger Box has the
given Alignment.
Arguments
| :: Orientation a | |
| => Alignment a | |
| -> Int | Number of rows |
| -> Int | Number of columns |
| -> Radiant | Background color for smaller box |
| -> Radiant | Background color for larger box |
| -> Text | |
| -> Box a |
Puts the given text in the center of a box. The resulting box is center aligned.
renderBox1 :: IO () Source #
box2 :: Box Horizontal Source #
renderBox2 :: IO () Source #
box3 :: Box Horizontal Source #
renderBox3 :: IO () Source #
box4 :: Box Horizontal Source #
renderBox4 :: IO () Source #
box5 :: Box Horizontal Source #
renderBox5 :: IO () Source #
Constructors
| Station | |
Fields
| |
allStationRows :: [[Cell]] Source #
renderVerticalStationTable :: IO () Source #
stationColumn :: Station -> [Cell] Source #
allStationColumns :: [[Cell]] Source #