Renderables · Term.jl

# Renderables

In the previous section we...

    using Term  # hidden
    tprint("[green]...have seen how to add some [gold3 bold underline]style[/gold3 bold underline] to our text")  # hidden

    ...have seen how to add some style to our text

and that's great, but it's not enough. If you want to create really beutiful and structured terminal outputs, a bit of color and bold text is not enough. You want to be able to create panels to separate different pieces of content, lines to mark out different sections, you want to be able to control the aspect (e.g.,, line length) of the content you're printing and, most importantly, you want to do all this without too many headaches. `Term.jl` has got your back.

In this section we will look at `Renderable` objects (subtypes of `AbstractRenderable`) such as `TextBox` and `Panel`. In the next page we will focus on how to compose multiple renderables into a layout and we'll introduce renderables such as `hLine` and `Spacer` that are best introduced in that context.

## AbsractRenderable

This section focuses a bit on how renderables work under the hood. If you just want use `Term` and you don't care too much for how it works, skip ahead to the next section!

When you venture beyond styling simple strings, virtually every object you'll encounter will be a subtype of the `AbstractRenderable` type. We will call these objects renderables. Renderable types vary, but they all must have two fields: `:segments` and `:measure`.

Segment & Measure

A `Segment` is simply a line of text, kinda. The segment type:

        struct Segment
            text::AbstractString   # text with ANSI codes injected
            plain::AbstractString  # plain text with no style
            measure::Measure       # measure of plain text
        end

stores a bit of plain text (i.e. without any style information) but also the same text with style information (`text`). Text is created as described earlier, using `apply_style`. The other bit of information is the `Measure` object. `Measure` keeps track of the size of objects as they will be rendered in the terminal (i.e., wihtout style markup or ANSI codes). It stores a width (`w`) and height (`h`) attribute keeping track of text width and number of lines. The `Measure` of a segment is just that: the `textwidth` of `Segment.plain` and the number of lines in it.

When creating a renderable. This will generally produce the content that will be ultimately be printed to te terminal by generating a list of `Segment`s. When the renderable is printed out, its `Segment.text`s are printed to the console in sequence. The `Measure` of a renderable is a combination of the `Measure` of the individual segments. It provides information about the renderable's width and number of lines, crucial when creating layouts!

## Renderable

The most generic renderable type is the creatively named `Renderable`. You'll very rarely create an instance of a `Renderable` from scratch. More generally `Term` will create one while performing another operation. For example: in the next page we'll see how to stack multiple renderables to crate a complex layout. Each renderable can be any `AbstractRenderable`-like object (including string). So if you're stacking a `Panel`, a `TextBox` and a `String`, what type should the resulting renderable object be? Well the generic but useful `Renderable` of course.

## RenderableText

Now we get to more interesting stuff: `RenderableText`. What is it? Exactly what the name says, a renderable that stores a bit of text:

    rend = RenderableText("""
        [bold red]Woah, my first [yellow italic]`Renderable`!
    """)
    print(rend)

        Woah, my first `Renderable`!

Nothing special here. You'll notice that `RenderableText` automatically applies style information though. Also, when we were just styling strings before we had to use `tprint` instead of the normal `print` function to get our styled output. Well no more! `Renderable` objects work well with `print` so you can drop that `t`.

Now, do we really need a whole new type just to print a bit of text? Of course not, but `RenderableText` does more than that!

    import Term: RenderableText

    rend = RenderableText("."^100; width=25)
    print(rend)

    .........................
    .........................
    .........................
    .........................

magic! When we pass a width argument `RenderableText` reshapes our input text to the desired width! As you can imagine, when you're creating a layout made up of multiple elements, you want to be able to control the width of each element, so here you go!

Now, as a reward for getting this far into the docs, a little sneak preview at renderables stacking:

    t1 = RenderableText(lorem; width=25)
    t2 = RenderableText(lorem; width=42)
    rend = t1 / "\n [bold green]second paragraph[/bold green] \n" / t2  # stacking syntax!!! - the result is typeof `Renderable`
    print(rend)

    Lorem ipsum dolor sit ame
    t, consectetur adipiscing
    elit, sed do eiusmod tem
    por incididunt ut labore
    et dolore magna aliqua. U
    t enim ad minim veniam, q
    uis nostrud exercitation
    ullamco laboris nisi ut a
    liquip ex ea commodo cons
    equat. Duis aute irure do
    lor in reprehenderit in v
    oluptate velit esse cillu
    m dolore eu fugiat nulla
    pariatur. Excepteur sint
    occaecat cupidatat non pr
    oident, sunt in culpa qui
    officia deserunt mollit
    anim id est laborum.

     second paragraph

    Lorem ipsum dolor sit amet, consectetur ad
    ipiscing elit, sed do eiusmod tempor incid
    idunt ut labore et dolore magna aliqua. Ut
    enim ad minim veniam, quis nostrud exerci
    tation ullamco laboris nisi ut aliquip ex
    ea commodo consequat. Duis aute irure dolo
    r in reprehenderit in voluptate velit esse
    cillum dolore eu fugiat nulla pariatur. E
    xcepteur sint occaecat cupidatat non proid
    ent, sunt in culpa qui officia deserunt mo
    llit anim id est laborum.

## Panel

Okay, time to move beyond simple text. It's time for:

    ╭─ Term's ───────╮
    │     awesome    │
    ╰────── Panels ──╯

Simply put, a `Panel` showing a piece of content (generally a styled string, but it can be any `Renderable` really) surrounded by a box. Simple but effective.

Well not that simple actually because `Panel` is the first renderable that allows you lots of options to personalize its appearance. For instance the panel printed above is given by:

        Panel(
            "[red]awesome[/red]",
            title="Term's",
            title_style="bold green",
            style="gold1 bold",
            subtitle="Panels",
            subtitle_style="bold blue",
            subtitle_justify=:right,
            width=18,
            justify=:center
        )

The first argument is the content, the rest is styling options. As you can see you can specify the titles and subtitles (or leave them out if you prefer, do your thing!), their appearance (via `markup` style information) and their position (`:left, :center` or `:right`). The `style` argument sets the style of the box itself (and title/subtitle if they don't have dedicated style information).

The box is created using `Term`'s own `Box` type! It's not worth going too much into exactly how it works, but it's worth pointing out that there's loads of types of boxes:

    ASCII,
    ASCII2,
    ASCII_DOUBLE_HEAD,
    SQUARE,
    SQUARE_DOUBLE_HEAD,
    MINIMAL,
    MINIMAL_HEAVY_HEAD
    MINIMAL_DOUBLE_HEAD,
    SIMPLE,
    SIMPLE_HEAD,
    SIMPLE_HEAVY,
    HORIZONTALS,
    ROUNDED,
    HEAVY
    HEAVY_EDGE,
    HEAVY_HEAD,
    DOUBLE,
    DOUBLE_EDGE

And you can use any of these with your panels:

    print(
        Panel(width=8, box=:DOUBLE, style="green") *
        Panel(width=8, box=:HEAVY, style="white") *
        Panel(width=8, box=:ROUNDED, style="red"),
    )

    ╔══════╗┏━━━━━━┓╭──────╮
    ╚══════╝┗━━━━━━┛╰──────╯

By the way, `Panels` are not limited to having strings as content, they can have other renderables too (multiple ones in fact)!

    print(
        Panel(
            Panel(width=18, style="green"),
            Panel(width=18, style="white"),
            Panel(width=18, style="red"),
            title="so many panels!",
            width=:fit,
            title_justify=:left,
            title_style="bold red"
        )
    )

    ╭─ so many panels! ──╮
    │ ╭────────────────╮ │
    │ ╰────────────────╯ │
    │ ╭────────────────╮ │
    │ ╰────────────────╯ │
    │ ╭────────────────╮ │
    │ ╰────────────────╯ │
    ╰────────────────────╯

## TextBox

`TextBox`es are a very simple but very useful renderable. They bring together `RenderableText` with `Panel`. In fact they're just a panel with a `RenderableText` inside and with its box hidden. Why do we need them, you say? Well because now you can have a piece of text, with a nice title and sub title.

    import Term: TextBox

    print(
        TextBox(
            ","^100 * "\n",
            title="title!",
            subtitle="sub title!",
            width=30,
            title_style="bold red",
            subtitle_style="dim",
            title_justify=:center,
        )
    )

    ╭───── title! ───────────────╮
    │ ,,,,,,,,,,,,,,,,,,,,,,,,,, │
    │ ,,,,,,,,,,,,,,,,,,,,,,,,,, │
    │ ,,,,,,,,,,,,,,,,,,,,,,,,,, │
    │ ,,,,,,,,,,,,,,,,,,,,,,     │
    │                            │
    ╰─ sub title! ───────────────╯

Okay, admittedly that's not huge. But it still nice to have. It also helps with keeping layout consistent when mixing panels and text, have a look:

    import Term: RenderableText, Panel

    t = ","^100

    text = RenderableText(t; width=22)
    panel = Panel(t, width=22)

    print(
        text, panel
    )

    ,,,,,,,,,,,,,,,,,,,,,,
    ,,,,,,,,,,,,,,,,,,,,,,
    ,,,,,,,,,,,,,,,,,,,,,,
    ,,,,,,,,,,,,,,,,,,,,,,
    ,,,,,,,,,,,,
    ╭────────────────────╮
    │ ,,,,,,,,,,,,,,,,,, │
    │ ,,,,,,,,,,,,,,,,,, │
    │ ,,,,,,,,,,,,,,,,,, │
    │ ,,,,,,,,,,,,,,,,,, │
    │ ,,,,,,,,,,,,,,,,,, │
    │ ,,,,,,,,,,         │
    ╰────────────────────╯

you see? The panel and the text have the same width, BUT, the panel must fit its box, some padding and its content within the same width. So the size of the text inside will need to change compared to `RenderableText(t; width=22)`. If we use a `TextBox` on the other hand:

    tbox = TextBox(t, width=22)
    panel = Panel(t, width=22)

    print(
        tbox, panel
    )

    ╭────────────────────╮
    │ ,,,,,,,,,,,,,,,,,, │
    │ ,,,,,,,,,,,,,,,,,, │
    │ ,,,,,,,,,,,,,,,,,, │
    │ ,,,,,,,,,,,,,,,,,, │
    │ ,,,,,,,,,,,,,,,,,, │
    │ ,,,,,,,,,,         │
    ╰────────────────────╯
    ╭────────────────────╮
    │ ,,,,,,,,,,,,,,,,,, │
    │ ,,,,,,,,,,,,,,,,,, │
    │ ,,,,,,,,,,,,,,,,,, │
    │ ,,,,,,,,,,,,,,,,,, │
    │ ,,,,,,,,,,,,,,,,,, │
    │ ,,,,,,,,,,         │
    ╰────────────────────╯

Now the two pieces of text look the same and the final layout is a lot more homogeneous, success!

Let's move on to talk about layout more!

Theme

This document was generated with Documenter.jl version 0.27.14 on Wednesday 9 March 2022. Using Julia version 1.6.5.