Revision history for Term::Fabulous

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.
