Changes for version 0.04 - 2026-10-19

  • A Widget::DocumentView counts the items added to its document as soon as it is asked: count, show_item, scroll_to_bottom and a search reach an item added since the last frame instead of dying with "item N does not exist". A code block the document gets after the view showed it takes the view's highlighter and the sideways offset of the other code blocks when the view builds it.
  • Term::Fabulous::Scrolling's scroll_to_end (and End, G and Ctrl+End of a document view, and a VirtualList with follow_end) scrolls a VirtualList to the end of its content once its last item is in view, so a last item taller than the view ends at the bottom of the view instead of starting at its top; before, such a list never reached its end and with follow_end never settled.
  • Widget::Table's highlight_text checks the offsets against the cell's text at the call, also for a row that is not built; before, offsets past the text died in the frame that built the row. Its estimated_rows counts the lines of its frame (the top, the line under the header rows, the bottom ledge) and the filter row, so a DocumentView holding a table no longer guesses it about three rows too short.
  • Widget::Accordion::Item's reveal_text opens the item through its accordion, so the item that was open closes unless multiple are allowed; before, revealing a match left both open.
  • Widget::Menu has the documented keys_color, separator_color, highlight_text_color and highlight_background_color accessors.
  • Widget::Dialog checks z_index against the range Clay has, -32768 to 32767, in new and in the accessor, instead of dying inside Clay::UI at open.
  • A Widget::Dropdown's list is never wider than the screen and cuts its labels with an ellipsis (fit_columns); before, a list wider than the screen was cut at the right edge with no mark.
  • examples/widgets/dialog.pl and examples/cookbook/confirm-dialog.pl use the dialog's own title, buttons and Answer; examples/widgets/prompt.pl and examples/cookbook/document-contents.pl escape with Term::Fabulous::Text::Markup::escape_markup; the Right key of examples/widgets/code-block.pl starts from shown_offset, so Left reacts at once after scrolling past the longest line.
  • Render::Geometry::cell_rect rounds every edge of a box to the nearest cell instead of flooring it. A layout at fractional positions (a percent-wide sidebar, say) gave two boxes that meet edges of 70.9999 and 71, which floored to different cells, so tables showed a blank column with broken lines between two cells and a body one cell wider than its header.
  • Widget::Tabs and Tabs::Bar have active_padding, the cells the active tab grows toward the page beyond the other tabs; it was always one cell and is 0 by default now, so all tabs are the same size unless asked otherwise.
  • The frame of a Widget::Table stays while its rows scroll: the bottom (and, without column titles, the top) is a ledge of its own below (above) the scrolling body, drawn in the table's line_color, instead of a side of the last (first) row. The scrollbar moved inside the frame, into a column right of the last column without a line of its own, which appears only while the rows do not fit, so a table that does not scroll is as wide as its columns. Table::Borders::resolve_borders takes ledge lines and a gutter flag and returns { lines, gutter }; Table::HeaderView is Table::EdgeView, the view of the header and of the footer.
  • Widget::estimated_rows counts only the border sides that are drawn (a Hidden side takes no room) and the room a ScrollBox keeps for its scrollbars, through the new content_padding (which ScrollBox extends), so a VirtualList or DocumentView no longer guesses such items too tall or too short.
  • A theme file whose palette token has child nodes is rejected with "palette token '...' needs exactly one string argument", as the theme editor's document always did; a test now runs every invalid and valid sample through both readers.
  • The theme editor's form keeps the kind, family and variant of each section row (ThemeEditor::Form::section_info) instead of the editor parsing them out of the row ids, and F6 focuses the first widget of the preview the way Tab orders them.
  • Term::Fabulous::Command::plain_label is the label without a trailing "..." (menus keep label), and the class method Term::Fabulous::Widget::KeyReference->command_rows($group, @commands) builds the key rows of commands; the key reference and the theme editor's key_list, commands and messages use them instead of stripping the dots themselves.
  • Term::Fabulous::Widget::Table::Model has two options for trees in sections: open_when_filtered (a filtered or searched view shows every passing row with its parents open, without changing which rows are expanded) and only_leaves_match (a row with children passes only as the ancestor of one that matches). The theme editor's settings list keeps its rows, sections, search, filters and cursor in a Table::Model with both, instead of a second tree model of its own; its row gives a copy without the children.
  • Term::Fabulous::Widget::Dialog owns the parts most dialogs have: a title, buttons (one row; pressing one calls press_button, which answers with its id), a notice line above the buttons (notice($markup, 'info' or 'error'), notice_text, and notice_line => 'shown', 'kept' or 'row' for the room it takes), placement => 'center' or 'right', answer($id, %values) and values. Every dialog now fires Answer after Close whenever it closes: with the button that answered, or as cancel and dismissed for Escape and close, carrying the dialog's values in both cases (a Prompt's fields, nothing for a FileDialog or a plain Dialog). Prompt, FileDialog, KeyReference and the theme editor's value dialog declare their titles and buttons instead of building them; the key reference answers close from its Close button, and the value dialog reports through Change (the preview) and Answer instead of its on_preview, on_apply and on_reset callbacks. The file dialog's notice wraps instead of cutting a long path from the left, and its FolderLine no longer has say, text or after. Prompt's errors for a closed prompt or an unknown button read "answer needs an open dialog" and "the dialog has no button with the id ...". Term::Fabulous::Text::Markup exports escape_markup.
  • Term::Fabulous::Placement::place_popup is the one rule for where a popup goes: below or above its anchor (flipping when it does not fit and the other side has more room, reporting the rows it has there) or at it, aligned left or right, and always kept on the screen; TOPMOST_Z_INDEX is the z_index of popups. Term::Fabulous::Role::Popup floats a widget from the root at the position it works out, and checks after each frame whether that frame moved it. The dropdown's list, popup menus and the table's column chooser compose it. A dropdown's list no longer runs off the right edge of the screen, and a column chooser near the bottom of the screen moves up instead of hiding its last check boxes; a dropdown's list also stays on the screen when the terminal shrinks while it is open, turning to the other side (with its arrow) when its side has no room left.
  • Term::Fabulous counts clicks: every Mouse press has a click_count, 2 for the second press of a double click, 3 for a triple click, for presses of the same button in the same cell at most double_click_seconds apart (a new parameter of Term::Fabulous->new, default 0.4) on the UI's clock. Table, TextField, TextArea and the theme editor's settings list read it instead of timing presses on the system clock, and the dropdown's type-ahead runs on the UI's clock too, so a test with its own clock can click or type slowly. Table's double_click_seconds parameter is gone; the UI's applies.
  • Term::Fabulous::Unicode::fit_columns($text, $columns, mark => ..., cut => 'end' or 'start') cuts a text to a number of columns with a mark where it was cut (an ellipsis, exported as ELLIPSIS, by default), never splits a grapheme cluster and is never wider than the room; a mark wider than the room is cut itself. The status bar, the document tabs, chart texts, the truncate mutator, the file dialog's folder line, the theme editor's settings list and the dropdown list cut through it. The settings list no longer splits an emoji from its variation selector, and the folder line's "..." no longer takes three columns where it has one or two. FileDialog::FolderLine's cut_from_the_left is gone.
  • Term::Fabulous::Text::Marks keeps the spans, highlights and links of one text, checks their ranges with one wording and cuts the text into runs in one sweep; Text and CodeBlock keep their marks in it and share their methods through Term::Fabulous::Role::MarkedText. Both now have spans as an accessor (Text gains the setter), stylize and clear_spans (CodeBlock gains them), highlights and clear_highlights. CodeBlock's range errors read like Text's ("span end 3 lies past the text (2 characters)", "highlighter spans must be an array reference ..."), and setting a code block's spans counts as a change of its content_revision, as on a Text.
  • Term::Fabulous::Role::Themed's drawing_theme (until now the private _theme) is the one answer to "the theme a widget draws with". Term::Fabulous::Document::Look->of asks it, so a CodeBlock under a theme_for subtree paints its background and clipped marker in the subtree's theme like its spans, and a DocumentView with a quote builds in a plain Clay::UI instead of dying. A Tabs::Button without a bar and an Accordion::Item without an accordion take the looks of their UI's theme instead of the default theme, and the item follows a theme switch.
  • Term::Fabulous::Role::SearchContainer: a container lists the searchable widgets it holds (searchable_parts) and may show one (reveal_part); the role answers the search for them. Tabs and Accordion::Item compose it. The revision of a nested key names the widget at its index (search_serial), so a search that kept a key into a page that was removed searches again instead of dying in highlight_text on the page that took its place. Role::Searchable exports nested_part.
  • Term::Fabulous::Scrolling has the keys of pagers: scroll_by_key($ui, $box, $key_name, keys => 'pager' or 'less') scrolls by rows, by pages or to the ends and returns undef for a key that is not in the set; scroll_offset and max_scroll_offset report the position in positive rows, and scroll_by_columns scrolls sideways. scroll_to_start and scroll_to_end send a VirtualList to its first or last item. A ScrollBox with follow_end => 1 keeps the end of its content in view until the user scrolls up, and follows again at the end. DocumentView, the theme editor, the pager, log, command output and showcase examples use them instead of their own key tables, timers and sign flips. The theme editor's custom preview pages by the rows its content shows, without its horizontal scrollbar, as every pager does.
  • Work after a frame is bound to a widget: after_draw takes an owner (held weakly, passed in, skipped once it is gone or out of the UI), the new each_draw calls its code after every frame until it returns true, its frames run out or its owner leaves, with a name so that a new request replaces the old one, and Term::Fabulous::Widget::each_frame registers such work for a widget (work asked for before the widget is in a UI waits for one). A callback that dies no longer drops the ones queued after it. ScrollBox has reveal($widget, align => 'nearest' or 'top'), which waits for the widget's layout by itself, and Scrolling's reveal_rows takes the same align. The widgets that kept their own watchers and retry counters (ScrollBox, VirtualList, DocumentView, Table, the column chooser and the theme editor's preview) use them.
  • The focus no longer vanishes when the focused widget is removed, disabled or hidden: it goes to its heir (Clay::UI 0.07 focus heirs), the nearest widget above it that can take the focus, or the widget a container names with focus_heir. Tabs names its active tab, so a field on a page that select or choose hides no longer leaves Ctrl+PageUp and Ctrl+PageDown without the Tabs; Accordion::Item names its header when it closes over a focused field; a Dialog's backdrop and a popup Menu name the widget that had the focus before they opened. Before, nothing had the focus and the keys went to the root widget.
  • A Text with wrap_mode CLAY_TEXT_WRAP_NONE stays on one line, as Clay::UI 0.07 lays it out: a "\n" in it is shown as the replacement character U+FFFD instead of starting a new line, and estimated_rows and row_of_offset count the whole text as one row. Before, it broke at newlines like CLAY_TEXT_WRAP_NEWLINES.
  • Text that does not wrap (wrap_mode newlines or none) is cut off when its parent is too narrow, once everything beside it has shrunk; a newlines text still breaks only at its newlines. A long word in a grow or percent Table column with wrap 'none' or 'newlines' no longer pushes the table past its parent. Needs the cut-off patch of Clay::UI 0.07.
  • Text in a grow or percent column of a Table wraps: such a table no longer scrolls sideways, so its columns get narrower when it is too narrow for them. Before, the text stayed on one line and the rows scrolled sideways.
  • Term::Fabulous::Widget::Table, Tabs and Accordion::Item do Term::Fabulous::Role::Searchable: a search reaches the cells on every page and under collapsed rows and groups, the pages a Tabs does not show and the bodies of closed accordion items, and revealing a match turns the page, expands the rows and groups, activates the tab or opens the item.
  • A widget that gets the focus inside a ScrollBox is scrolled into view, and a VirtualList whose window drops the focused widget hands the focus to the list or the nearest widget above it that takes it. Clay::UI 0.07 gives text widgets a bounding box, so a menu opens below a text too.
  • Term::Fabulous::Widget::Text absorbs RichText: one text widget with spans, links, markup and the keys and looks of links; the RichText and FocusableText classes are gone. A text also has highlights, a layer over its spans that a search marks without touching them, a content_revision for callers that recorded offsets, and an estimated_rows for lists that need its height before a layout. A color in any style may be a palette token of the theme (bold accent on surface_low), looked up when the text paints, so a theme switch recolors it. Term::Fabulous::Role:: Searchable names the texts a widget shows, for search and highlights; Text and CodeBlock do it. Every widget estimates its rows before a layout (Term::Fabulous::Widget::estimated_rows), boxes from their children. Render::Frame::text_rows gives the painted lines of a text.
  • Term::Fabulous::Widget::DocumentView shows a document whose items are any widgets: headings, paragraphs, lists, quotes and code from the factories of Term::Fabulous::Document, and tables, pictures or forms added as they are. It scrolls with the keys, selects the links of every text with l and L and follows them with Enter or a click, searches the text of every item (table cells on other pages included), lets Tab reach the buttons and fields inside, and fires a ViewChange after every frame that shows something new. With it the document (Term::Fabulous::Document: items, sections, anchors, find_text), its styles (::Document::Look, as palette tokens), the scrolling functions of Term::Fabulous::Scrolling, Term::Fabulous::Widget::CodeBlock and Term::Fabulous::Event::ViewChange. Extracted from Pod::Fabulous, so that readers for other formats share them.
  • Two new cookbook pages. Term::Fabulous::Cookbook::Documents builds a document in Perl and from Markdown read with Markdent, follows links and searches; Term::Fabulous::Cookbook::DocumentTechniques shows a table of contents, highlights code, changes the styles and adds mark kinds, pages through any scroll box, and puts a table and a picture into a document and searches into the table.
  • Fixes from CPAN Testers: on perls built with long doubles, the labels of a radar chart at a quarter turn no longer land one row off (Term::Fabulous::Chart::Radial::point_at returns whole coordinates as whole numbers), and animation_frame counts a clock a rounding error short of a frame boundary as that frame.

Documentation

Edit Term::Fabulous themes with a live preview
Recipes for common Term::Fabulous tasks
Recipes: draw on a canvas
Recipes: curves, markers, colors, line styles and hover of charts
Recipes: time axes, live data, transforms, KDL and reports with charts
Recipes: one for each chart type
Recipes: a table of contents, syntax highlighting, styles, pager keys and any widget in a document
Recipes: build documents in Perl and from Markdown, follow links, search
Recipes: your own widgets and events
Recipes: forms, dialogs and input widgets
Recipes: a first program, quitting and text
Recipes: key bindings and buttons
Recipes: layout, borders, colors and themes
Recipes: timers, logs and the output of commands
Recipes: menu bars, context menus, status bars, help screens and document tabs
Recipes: reports and tests without a terminal
Recipes: sort, filter, group, nest and page table rows
Recipes: column sizes, lines and colors of tables
Recipes: show, format, edit and print tables
The example programs of the distribution, with pictures
The Term::Fabulous user guide
Canvases and charts
Writing your own widgets
Events, keyboard, focus, mouse and scrolling
Progress bars, spinners, toasts, status bars, prompts and file dialogs
Forms and input widgets
The terms used in the Term::Fabulous documentation
KDL layout files
Widgets, the widget tree and layout
Text, colors and borders
Commands, menu bars, context menus and key references
The event loop, output without a terminal and testing
Tables: sorting, filtering, grouping, trees, pages and selection
Tables: colors, lines, size and printing
Tables: how they work, rows, columns, display text and cell widgets
Solutions to common problems

Modules

Full-screen terminal user interfaces with layouts, widgets, keyboard and mouse
How lines connect the points of a series
Easing functions for the curves of charts
Number and date labels of charts
The character sets charts draw with
Color palettes and color arithmetic for the chart widgets
Geometry of round charts in terminal cells
A drawing surface with several subpixels per terminal cell
What the scales of chart axes have in common
An axis of named categories
A numeric axis with evenly spaced ticks
A logarithmic numeric axis
A date and time axis
One data series of an XY chart
The cells of a chart while it is drawn
Prepare the data of a chart series: normalize, smooth, accumulate, resample
Validate the values of widget properties
An immutable RGBA color, parsed from many notations
Something the user can do, with a name and keys
The commands of a program, and the keys that run them
A document: a list of widgets with the sections and the layout a document view shows them with
Styles for each kind of document text, taken from a theme
Text, cursor, selection, undo and clipboard of a text input
The border styles a widget can be drawn with
The CSS named colors as Term::Fabulous::Color objects
The user activated a button
The user answered a dialog
A canvas got a new size
The user changed the value of an input widget
The user chose an item of a menu
A dialog, a toast or a popup menu went away
The user closed a row or a group of a table
The user chose which columns of a table are shown
The cursor of a table moved to another line
The user opened a row or a group of a table
The user typed into a filter field of a table
A key press from the terminal
The user followed a link of a Text
A mouse click, release, drag or wheel turn from the terminal
The mouse pointer moved with no button held
The user turned the page of a table or changed its page size
The terminal changed size
The user activated a row of a table
The user opened or closed an item of an accordion, or chose a tab
The user changed which rows of a table are selected
The mouse moved onto another series, point or slice of a chart
The user changed how a table is sorted
The terminal is open and its size is known
The user pressed Enter in a text field
The user asked for a new document tab
The user asked to close a document tab
A mouse button pressed on a character of a text
The value of an input widget became valid or invalid
Reports that what a document view shows has changed
Build a widget tree from a KDL layout description
The options of a choice and the one selected
Where a popup goes on the screen
A value in a range, on a grid of steps
Role that paints Clay render commands into terminal cells
Turn colors into termbox2 truecolor attributes
Paint widget borders in their border styles
Paint canvas widgets, only their changed cells when possible
What one frame paints, command by command
Snap Clay's layout boxes to terminal cells, and step lines through a raster
Paint widget backgrounds
Cell target that keeps the painted cells in memory
Base role of the cell targets: protect kept cells during a frame
Role of the cell targets that show sixel pictures
Paint lines of text
Let a widget class be built from a KDL layout
Border glyphs per side, and borders that take space
A widget whose options come from a layout
A widget whose range comes from a layout in one piece
Named data series of a chart
The spans and highlights of a widget that shows one text
A floating widget that keeps its place on the screen
A widget that answers a search for the searchable widgets inside it
The texts of a widget, for search and highlights
What Term::Fabulous needs from a terminal
How a widget reads its colors and borders from the theme
An input widget whose value can be checked
Which entry an arrow key moves to
Scroll a container by rows, by pages and to its ends, as a pager does
Render a widget tree to text once, without a terminal
termbox2 compiled into Term::Fabulous
One termbox2 input event
A terminal in memory, for tests
The real terminal, through termbox2
Cell target that paints into the terminal through termbox2
The spans, highlights and links of one text
Parse "[bold red]text[/]" markup into text, styled spans and links
Parse style strings such as "bold red on #202020"
Lay out the text of an editor in a view of rows and columns
Colors and borders for every widget, with variants per class
A theme file you can read, check and change, keeping its comments and layout
Edit Term::Fabulous themes with a live preview of every widget
The form mode of the theme editor
The text area of the theme editor's text mode
The gallery of widgets in the theme editor's preview
The table of settings in the theme editor's form
A theme open in the theme editor
The UI of the theme editor, with a preview in another theme
The dialog that edits one setting in the theme editor
Measure text in terminal cells, exactly as termbox2 draws it
Checks the value of an input widget
The arithmetic of a scrolled view
Abstract base class of the Term::Fabulous container widgets
Sections that open and close under their headers
One section of an accordion
Lines with the area below them filled
Values as bars, grouped, stacked or horizontal
The general-purpose container widget
A box that can be clicked, focused and activated
A widget you draw on cell by cell
What all chart widgets have in common: title, legend, colors and hover
A box the user can check and uncheck
Unwrapped code on its own background, clipped and scrolled sideways
A field, sliders and swatches for choosing a color
A box that opens over the whole screen, keeps the focus and answers
The screen-filling layer behind an open dialog
Common base class of the widgets that paint themselves from their own state
A line between widgets, with an optional text
A row of tabs for the open documents of a program
A scrollable, searchable view of a document of any widgets
A pie chart with a hole
Choose one of several options from a list that opens
The option list of an open dropdown
The Clay::UI roles behind Term::Fabulous::Widget
A dialog that asks for a file to open or save, or for a folder
The line that names the folder a file dialog shows
How values are distributed: counts in bins
Show a picture in half-block pixels
Common base class of the input widgets
A dialog that lists the keys of a program and what they do
A chart of lines through data points
A list of commands, as a context menu, in a menu bar or on its own
A row of menus at the top of the screen
A pie chart: parts of a whole as slices
A canvas of square-ish pixels, two per cell
Slices of equal angle whose length shows their value
How much of a task is done
A dialog that asks a question and waits for the answer
Several values per series on axes around a center
One choice of a radio group
A group of radio buttons of which one is selected
Points by two numbers, with trend lines
A box whose content scrolls
A scrollbar for a scroll container
Choose one of a few options shown side by side
Show a picture in sixel graphics
Choose a number from a range by moving a thumb
A small chart without axes, one row high
Show that something is going on
Rate something with a row of stars
A row at the bottom of the screen for messages and the program's state
Rows and columns with sorting, filtering, grouping, trees, pages and selection
Work out the grid lines of a table
What a table column shows and how
The list where the user picks the columns of a table
The header and the footer of a table, scrolled sideways with its body
Conditions that decide which rows a table shows
A frame around a table that shows its focus
The rows of a table and how it shows them
Ready-made mutators for table columns
The page controls below a table
Check the style hashes of a table
The marker that opens and closes a row or group of a table
Read numbers and dates from table values
Pages behind a row of tabs
A row of tabs, one of them active
The button of one tab in a tab bar
The line that joins the tabs of a bar with the page
One page of a Tabs widget
A piece of text inside a box: plain, with styled spans, highlights and links
Multi-line text input
Single-line text input
Common base class of the text input widgets
The Clay::UI roles behind Term::Fabulous::Widget::Text
A notification that appears in a corner and goes away by itself
The column of toasts in one corner of the screen
A ScrollBox that attaches only the items near the viewport
The common base of charts with an x and a y axis

Examples