NAME

Term::Fabulous::Render - Role that paints Clay render commands into terminal cells

SYNOPSIS

use Object::Pad 0.825;
use Clay::UI;
use Term::Fabulous::Render;
use Term::Fabulous::Render::Target::Grid;

# A UI class that paints into memory and never sees a pointer.
class My::Snapshot :isa(Clay::UI) :does(Term::Fabulous::Render) {
	field $cell_target :reader = Term::Fabulous::Render::Target::Grid->new;

	method pointer_state () { return undef }
}

my $ui = My::Snapshot->new( root => $root, width => 10, height => 2 );
$ui->draw;
my ( $glyph, $fg, $bg ) = @{ $ui->cell_target->cell( 0, 0 ) };

DESCRIPTION

Most programs never use this module directly. Term::Fabulous (for the terminal) and Term::Fabulous::Static (for text output) already compose it. Read on if you want to write your own UI class, for example one that paints into a different kind of output, or want to know exactly how a frame is painted. To write a widget of your own you do not need this module: build it on the existing widgets as Term::Fabulous::Manual::CustomWidgets explains (a widget that draws itself is a Term::Fabulous::Widget::Canvas).

Term::Fabulous::Render is an Object::Pad role for a subclass of Clay::UI. It turns a laid-out widget tree into terminal cells: "draw" asks Clay::UI for the frame's render commands (rectangles, borders, text, clipping and canvases) and paints each of them, cell by cell. Where the cells go is decided by an object the class provides, the cell target (see "CELL TARGET").

The role is composed of smaller roles, one per kind of render command: Term::Fabulous::Render::Rectangle, Term::Fabulous::Render::Border, Term::Fabulous::Render::Text and Term::Fabulous::Render::Canvas. What a frame paints where is worked out once per frame, before any painting, by Term::Fabulous::Render::Frame.

When it is constructed, the role checks output_mode and installs its own measure-text callback in Clay::UI (measure_text), which reports text widths in terminal columns (see Term::Fabulous::Unicode). The constructors of Term::Fabulous and Term::Fabulous::Static die when given a measure_text of their own.

REQUIREMENTS OF THE CONSUMING CLASS

The class that composes this role must provide:

CONSTRUCTOR PARAMETERS

output_mode

The termbox2 output mode. It must be TB_OUTPUT_TRUECOLOR (from Term::Fabulous::Termbox), which is also the default, because Term::Fabulous always paints 24-bit colors. Any other value dies with Term::Fabulous::Render: output_mode must be TB_OUTPUT_TRUECOLOR. There is no reason to pass it.

theme

The Term::Fabulous::Theme the widgets draw with: a theme object or the name of a built-in theme (dark, light). Default: dark. See "theme".

METHODS

theme

my $theme = $ui->theme;
$ui->theme('light');
$ui->theme( Term::Fabulous::Theme->from_file('ocean.kdl') );

Accessor for the theme. Without an argument it returns the Term::Fabulous::Theme object; with one it sets the theme (an object or a built-in name; anything else dies), makes every widget read its looks again, calls looks_changed on every widget of the tree that has such a method (see "looks_changed" in Term::Fabulous::Role::Themed) and draws a frame. Widgets that were given a color or a border style explicitly keep it; see "THEMES" in Term::Fabulous::Manual::Looks.

theme_for

my $theme = $ui->theme_for($widget);

The theme a widget of the tree draws with: the one "theme" returns. Term::Fabulous::Role::Themed asks this method whenever a widget looks its looks up, so a subclass of the UI class can override it to give a part of its tree another theme, for example a preview of a theme next to the controls that edit it (Term::Fabulous::ThemeEditor does that):

class My::UI :isa(Term::Fabulous) {
	field $preview :param;          # a Box: the part drawn in $preview_theme
	field $preview_theme = 'light';

	method theme_for :override ($widget) {
		return $self->SUPER::theme_for($widget) unless $preview->contains($widget);
		return Term::Fabulous::Theme->builtin($preview_theme);
	}

	method preview_theme ($name) {
		$preview_theme = $name;
		$self->refresh_looks;    # what theme_for returns changed
		return $self;
	}
}

The override must be cheap (it runs for every widget once after every change of the looks) and must call "refresh_looks" whenever what it returns changes. The screen background always comes from "theme".

refresh_looks

$ui->refresh_looks;

Makes every widget read its looks again, calls looks_changed on every widget of the tree that has such a method and draws a frame, as setting "theme" does. Call it after a change that "theme_for" reports. Returns the UI.

draw

$ui->draw;
$ui->draw( scroll_cells => [ $columns, $rows ] );

Lays out and paints one frame:

  1. Calls Clay::UI's render, passing the pointer from "pointer_state" (when it is defined) and the scroll amount. Clay::UI fires its pointer events (hover, press, scroll) during this call.

  2. Builds the Term::Fabulous::Render::Frame of the commands: their paint order, the clip rect of each and the cells each paints. "last_frame" returns it from now on.

  3. Sizes every canvas to its new content box and decides which canvases can keep the cells of the previous frame ("plan_canvases" in Term::Fabulous::Render::Canvas). Canvases fire CanvasResize here.

  4. Calls the cell target's begin_frame, paints the screen background (see "SCREEN BACKGROUND"), paints every render command in paint order, hands a target that shows sixel the frame's pictures (see "show_sixels") and calls end_frame.

  5. Calls "finish_canvases" in Term::Fabulous::Render::Canvas, which remembers this completely painted frame for the comparison in step 3 of the next frame. If painting died, this step is skipped and the next frame paints every canvas in full.

  6. Calls the code references queued with "after_draw", in the order they were queued. If painting died, they stay queued for the next frame.

scroll_cells scrolls the scroll container under the pointer (see Term::Fabulous::Widget::ScrollBox) by [$columns, $rows] cells before the layout is computed. Positive values reveal content above and to the left (the content moves down and right on screen), as turning a mouse wheel up does; negative values reveal content below and to the right. Clay keeps the result within the content. Term::Fabulous passes the wheel notches since the last frame here.

Any other argument dies. Render command types other than rectangle, border, text, scissor (clipping) and custom (canvas) die (see "new" in Term::Fabulous::Render::Frame); Term::Fabulous widgets produce only these. With the termbox2 cell target, the terminal must have been opened ("run" in Term::Fabulous and "step" in Term::Fabulous do that); otherwise termbox2 ignores the drawing.

after_draw

$ui->after_draw( sub {
	my $box = $ui->bounding_box($row) // return;
	...;    # the geometry of the frame just drawn
} );

Queues a code reference that "draw" calls once, with no arguments, after the next frame has been painted completely. Use it for work that needs the layout of a frame that has not been drawn yet, for example to scroll a row into view that was only just added: Clay::UI's bounding_box and scroll_state then answer for that frame. A change the callback makes (a scroll position, a widget property) shows in the frame after it, which is due at once. A callback may queue another one; it runs after the following frame. Anything but a code reference dies. Returns the UI object. An exception from a callback leaves draw with it; the callbacks queued after it are dropped.

last_frame

my $frame = $ui->last_frame;
my @indices = $frame->topmost_at( $x, $y );

The Term::Fabulous::Render::Frame of the last "draw": its render commands in paint order, the clip rect of each and the cells each painted. Term::Fabulous hit-tests every mouse report with it. Before the first frame, an empty Frame of the current size. The Frame is complete even when painting the frame died partway, so it always describes one whole layout. Use $ui->widget_for( $command->{userData} ) to get the widget a command belongs to.

clip_rect

my ( $x0, $y0, $x1, $y1 ) = @{ $ui->clip_rect };

The rectangle of cells the render command being painted may touch, as a new array reference (see "clip_rect" in Term::Fabulous::Render::Frame). The paint roles call it from their render command handlers; it dies when no command is being painted (Term::Fabulous::Render: clip_rect is only known while a render command is painted).

pointer_state

method pointer_state () { return { x => 12, y => 3, down => 0 } }

Required from the consuming class: undef when there is no pointer, or a hash reference with the pointer's cell x and y (from 0) and whether the left button is held (down, 0 or 1).

"draw" gives Clay the center of that cell (x + 0.5, y + 0.5), not its corner: Clay counts the right and bottom edge of a box as part of the box, so the corner of a cell would also be "over" the widgets to the left of and above it. As a consequence, the x and y of Clay::UI's OnPress and OnRelease events are cell centers, such as 12.5.

HOW COMMANDS ARE PAINTED

Clay positions boxes in fractional layout units. They are snapped to whole cells (see "cell_rect" in Term::Fabulous::Render::Geometry) and clipped to the viewport (width x height) and to the innermost scissor around the command (see Term::Fabulous::Render::Frame). Nothing outside these limits is painted. Colors are turned into termbox2 attributes as described in Term::Fabulous::Render::Attr; alpha 0 means "no color", and only a background with an alpha from 1 to 254 is blended.

Rectangles

A widget's background. Its cells are filled with spaces in the background color. A translucent background is blended with what lies below it, covering the glyphs there or letting them show through as the widget's glyphs_show_through says. A widget whose reverse_video is true (a pressed Term::Fabulous::Widget::Button) adds reverse video to its background, so everything painted on it later swaps its colors. See Term::Fabulous::Render::Rectangle.

Text

One line of a Text widget, in its text color plus its bold, italic and underline style bits. It starts at the top-left cell of its box; every grapheme cluster takes as many columns as termbox2 will use for it. A cluster that would cross the right edge of the box or of the clip area ends the line. The background of each cell is whatever was painted there before. See Term::Fabulous::Render::Text.

Canvases

The cells of a Term::Fabulous::Widget::Canvas, painted into its content box. Clay carries a canvas's background in the canvas's own command; the frame paints it as a rectangle before the canvas, so the background lies below the cells. See Term::Fabulous::Render::Canvas.

Borders

The border of a widget composing Term::Fabulous::Role::HasBorderStyle, in its border styles. See Term::Fabulous::Render::Border.

Scissors

Start and end of a clipping area, for example around the content of a scroll container. They paint nothing themselves; the Frame turns them into the clip rects of the commands between them. See Term::Fabulous::Render::Frame.

SCREEN BACKGROUND

method screen_background () { return $color }    # a Term::Fabulous::Color, or undef

Before the render commands of a frame are painted, the whole viewport (the screen, or the rows of an inline region) is filled with spaces in the color the consuming class returns from screen_background, and that color is recorded as the background below every cell. So the widgets, their borders and their translucent backgrounds are painted over it, not over whatever the terminal shows where a program sets no color, and a theme made for a light background is readable on a dark terminal. The cells a canvas keeps from the last frame are left alone. A translucent color is painted opaque here, since there is nothing below it to blend with. Widgets that need the color they lie on (the unset cells of a canvas, the cells of a table, the ink of a chart) ask "background_below" in Term::Fabulous::Widget, which ends at this color.

Term::Fabulous returns the background token of its theme, or undef for a token with alpha 0, which leaves the terminal's own background (see "Tokens" in Term::Fabulous::Theme). Term::Fabulous::Static returns undef: its lines are printed into whatever the terminal shows.

CELL TARGET

method cell_target () { return $grid }

The paint roles do not write to the terminal themselves. They compute a glyph and two termbox2 attributes per cell and hand them to the cell target, the object the consuming class returns from cell_target. The renderer asks for it once per render command, so cell_target should return a stored object, not build one. The distribution has two:

Term::Fabulous::Terminal::Termbox::Cells

Draws into the terminal through termbox2. Term::Fabulous paints into the cell target of its terminal ("cell_target" in Term::Fabulous::Role::Terminal), which is this one for the real terminal.

Term::Fabulous::Render::Target::Grid

Keeps the cells in memory. Used by Term::Fabulous::Static and by Term::Fabulous::Terminal::Memory.

Both compose Term::Fabulous::Render::Target::Mask, which implements all methods below except painted_cell on top of a few primitives; write your own target the same way, and give it a painted_cell of its own. All coordinates are cells, counted from 0 at the top-left, and always lie inside the viewport and the clip rect of the command: clipping happens before a target method is called.

begin_frame

$target->begin_frame(@kept_rects);

Called once before the commands of a frame are painted. Resets every cell outside the given [x0, y0, x1, y1] rectangles (all cells when none are given). The cells inside them must keep what the previous frame painted there, and writes into them are ignored until the rectangle is released with "release_rect".

end_frame

$target->end_frame;

Called once after all commands of a frame are painted. Releases all kept rectangles and shows the frame.

release_rect

$target->release_rect($rect);

Stops protecting one of the rectangles given to "begin_frame" (identified by being the same array reference). The canvas that owns it then paints its changed cells into it.

set_cell

$target->set_cell( $x, $y, $glyph, $fg, $bg );

Paints one cell: $glyph is a character string with one character (the base character of a grapheme cluster), $fg and $bg are termbox2 attributes.

extend_cell

$target->extend_cell( $x, $y, $character );

Appends a combining character (a character string of length one) to the cell set last at that position, to complete a grapheme cluster.

fill_row

$target->fill_row( $x, $y, $columns, $bg );

Paints $columns cells of spaces with the background attribute $bg, starting at ($x, $y) and going right.

painted_cell

my ( $glyph, $fg, $bg ) = $target->painted_cell( $x, $y );

Not part of Term::Fabulous::Render::Target::Mask: every target provides it itself. Term::Fabulous::Render::Rectangle calls it to repaint the glyphs below a translucent background. Reads back what the frame holds at a cell so far: the glyph (a character string, the base character plus any combining characters), its foreground and its background attribute. Returns an empty list when nothing was painted there. The cell to the right of a wide glyph is not written for it, so reading it gives what was painted there before the glyph. Kept rectangles do not affect reading.

show_sixels

$target->show_sixels(@placements);

Only for a target that composes Term::Fabulous::Render::Target::Sixel, which both targets of the distribution do. The renderer calls it once per frame, after the render commands are painted and before "end_frame", with the sixel pictures of the frame's canvases ("sixel_placements" in Term::Fabulous::Render::Canvas), none when its sixel_cell_size is empty. The role describes the placements and the other two methods it requires.

SEE ALSO

Term::Fabulous, Term::Fabulous::Static, Clay::UI, Term::Fabulous::Render::Frame, Term::Fabulous::Render::Target::Mask, Term::Fabulous::Render::Attr.