Revision history for Term::Fabulous
0.05 2026-10-19
- Fix from CPAN Testers: on perls built with long doubles, a press
exactly double_click_seconds after the one before continues
the run of clicks, as documented; the difference of the two
clock readings rounded a hair above the interval there and
started a new run.
- A Term::Fabulous::Range snaps a number exactly halfway between
two grid values to the higher one on every perl (0.35 with a
step of 0.1 gives 0.4); before, the rounding noise of the
division by the step decided, and perls built with long
doubles went up where the others went down.
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.
0.03 2026-10-15
- Fixes from CPAN Testers: the theme editor no longer needs the PNG
support of Imager; its preview picture is a TGA, which Imager
reads without a plugin. The image, sixel, screenshot and
terminal tests skip when Imager was built without PNG support or
without the freetype driver instead of failing.
- The main documentation carries an AI disclaimer.
0.02 2026-10-12
- Term::Fabulous runs on Windows: termbox2 gets a Windows console
backend (src/patches), reached through the console in virtual
terminal mode, and run polls the terminal for events there,
since Perl's select watches only sockets on Windows. The
warning about a locale whose character set is not UTF-8 now
comes from the real terminal when it opens, not from run, so
programs on a memory terminal no longer see it; the Windows
console is switched to UTF-8 and does not warn.
- Term::Fabulous::Termbox: tf_input_ended replaces
tf_readable_bytes and answers the end-of-input question for
every platform.
- run polls a terminal that offers no read handles at the rate of
the new termbox_input_poll_interval reader (1/60 second); such
a terminal's input was never read by run before.
- Fixes from CPAN Testers: a terminal that hung up is recognized
on FreeBSD too (ENXIO besides EIO); the time axis test no
longer depends on the language of the locale.
- Data::Checks and Object::Pad::FieldAttr::Checked are no longer
prerequisites; nothing has used them since the architecture
review of 2026-10-05. Without them, no XS extension of
Object::Pad is needed, which matters on Windows, where such
extensions cannot be built.
0.01 2026-10-11
- First release. Full-screen terminal user interfaces from a
tree of widgets, built in Perl or in a KDL layout file, laid
out by Clay (through Clay::UI) and drawn with 24-bit colors
through termbox2. Key presses, mouse clicks, wheel scrolling
and resizes arrive as events; keyboard focus moves with Tab
and the mouse.
- Widgets: boxes, text and rich text with styled spans, scroll
boxes and virtual lists, text fields and areas, buttons,
check boxes, radio groups, dropdowns, sliders, star ratings,
segmented controls, tables, tabs, accordions, dividers,
dialogs, toasts, progress bars, spinners, canvases, pixel
canvases, images and sixel pictures.
- Charts drawn from data: line, area, bar and scatter charts,
histograms, sparklines, pie, donut, polar area and radar
charts.
- Application widgets: menus, a menu bar, a status bar, prompts,
a command table with key bindings, a key reference, file
dialogs, color pickers and document tabs.
- Themes in KDL files, with a theme editor.
- An inline mode that uses a few rows below the shell's output
instead of the whole screen.
- Term::Fabulous::Static prints a widget tree once as text, with
or without colors, and Term::Fabulous::Terminal::Memory runs
whole programs in tests without a terminal.
Keyboard Shortcuts
Global
s
Focus search bar
?
Bring up this help dialog
GitHub
gp
Go to pull requests
gi
Go to GitHub issues (only if GitHub is preferred repository)