NAME
Term::Fabulous::Role::CanParseLayout - Let a widget class be built from a KDL layout
SYNOPSIS
package My::Panel;
use v5.32;
use warnings;
use feature 'signatures';
no warnings 'experimental::signatures';
use Object::Pad 0.825;
use Term::Fabulous::Widget::Box;
# Box already composes Term::Fabulous::Role::CanParseLayout.
# This class only stores what the layout says; drawing the title is
# up to the class (see examples/custom-widget.pl for a full widget).
class My::Panel :isa(Term::Fabulous::Widget::Box) :strict(params) {
field $title :param :accessor = '';
field $title_color :param :accessor = [ 255, 255, 255, 255 ];
field @shortcuts;
# The properties a layout may set, and how each is read:
# title "Settings", title_color "#ffcc00", shortcut key="F2" action="save".
method layout_properties :common () {
return (
$class->SUPER::layout_properties,
title => 'scalar',
title_color => 'color',
shortcut => \&_parse_shortcut,
);
}
method _parse_shortcut ($kid) {
my $props = $self->kdl_properties( $kid, qw(key action) );
push @shortcuts, [ $props->{key}, $props->{action} ];
return;
}
method shortcuts () { return @shortcuts }
}
1;
and in a layout:
use My::Panel as Panel
Panel "settings" {
title "Settings"
title_color "#ffcc00"
bordered #true
shortcut key="F2" action="save"
shortcut key="F10" action="quit"
}
DESCRIPTION
Term::Fabulous::Layout builds a widget tree from a KDL document. For every widget node it constructs the widget with only its id, and then hands the node to the finished widget:
my $widget = $class->new( id => $id );
$widget->apply_layout_node($node);
This role provides "apply_layout_node". It reads every property node of the widget's node as the class declares it in "layout_properties", and then applies them all through "apply_layout_settings". Since the widget is fully constructed by then, a layout sets its properties exactly like a program calling the accessors after new: every check and default of the constructor has run, and nothing depends on the order in which roles and subclasses are built. A class can only be used in a layout when it composes this role; Term::Fabulous::Layout checks that when the layout declares the class with use.
All widget classes of Term::Fabulous compose the role (through Term::Fabulous::Widget::Box, or directly as Term::Fabulous::Widget::Text does). To make your own widget usable in layouts, the easiest way is to subclass Box or one of its subclasses, as in the SYNOPSIS: you inherit the parsing of layout, sizing, padding, border and the color properties, and only add your own.
Property nodes and child nodes
Inside a widget's block, a node whose name starts with an uppercase letter (Text, Box, ...) is a child widget; Term::Fabulous::Layout builds it and adds it with add_child after the widget's properties were applied. Every other node (text, _note, 1st, ...) is a property of the widget. Both sides use the same rule, the function Term::Fabulous::Role::CanParseLayout::is_widget_node_name($name).
REQUIRED METHODS
layout_properties
method layout_properties :common () {
return (
$class->SUPER::layout_properties,
title => 'scalar',
collapsed => 'boolean',
accent => 'color',
shortcut => \&_parse_shortcut,
);
}
A class method (:common) returning the properties a layout may set, as pairs of a name and how its node is read:
'scalar'-
The node's value, read with "kdl_value": its single argument (
title "x") or a hash reference of itskey=valuepairs (a nodename a=1 b=2gives{ a => 1, b => 2 }). 'boolean'-
The node's single argument, read with "kdl_boolean": a layout must write
#trueor#false(or1and0), so a quoted"false"dies instead of counting as true. 'border_sides'-
The sides of a border, read with "kdl_border_sides": one boolean argument for all four sides (
bordered #true) or booleankey=valuepairs under side names (bordered top=#true bottom=#true). 'color'-
The node's value, read with "kdl_value" and turned into
[r, g, b, a]with "color" in Term::Fabulous::Check, so a layout can write any color string ("#ffcc00","rgb(255, 204, 0)","hsl(48, 100%, 50%)","Gold"; see "A string" in Term::Fabulous::Color). - a code reference
-
A structured property: the code is called as a method with the property node,
$self->$code($kid), and parses and applies the node itself, typically with the helpers below. Use it for nodes that do not fit the other kinds, such as an argument together withkey=valuepairs.
Each name of the first four kinds is also the name of the accessor that sets it: the value is applied as $self->name($value), so the accessor checks it, as for a program. This table is the only way a layout can set a value: a property that is not in it dies with the list of known names, so a layout file can neither call arbitrary methods nor silently ignore a misspelled property. A subclass returns its parent's table ($class->SUPER::layout_properties) plus its own pairs; a later pair for a name replaces the parent's. Any other kind dies when a layout is applied.
The themed parameters that have a kind (see "themed_params" in Term::Fabulous::Role::Themed) and the looks a widget forwards to its parts are layout properties by themselves: a color kind is a 'color' property, the border_sides kind a 'border_sides' one, every other kind a 'scalar' one. They need no entry here; an entry of the same name replaces theirs.
METHODS
apply_layout_node
$widget->apply_layout_node($node);
Applies the properties of a Text::KDL::XS::Node to the widget and returns the widget. Called by Term::Fabulous::Layout right after new. It reads every property node in the order of the layout (child widget nodes are skipped) into a setting, [ $name, $value ]: the value read as "layout_properties" declares it, or for a structured property the node itself. Then it calls "apply_layout_settings" with all of them. Dies when a property is unknown (the message lists the known names), when a node's shape is wrong, or when an accessor rejects a value; Term::Fabulous::Layout adds the widget's name and id to the message.
apply_layout_settings
method apply_layout_settings :override (@settings) {
my %range = map {@$_} grep { $_->[0] =~ /\A(?:min|max)\z/ } @settings;
$self->set_range(%range) if %range;
return $self->SUPER::apply_layout_settings( grep { $_->[0] !~ /\A(?:min|max)\z/ } @settings );
}
Applies the settings in the order given: an accessor call for a simple property, the handler for a structured one. Override it to apply related values together, so that a layout may give them in any order: Term::Fabulous::Widget::Dropdown sets its options before the value that picks one of them. Pass the other settings on to SUPER::apply_layout_settings. For a range (min, max, step and the like, set through one range setter) compose Term::Fabulous::Role::HasRange, which does exactly that, as Term::Fabulous::Widget::Slider does.
HELPERS
These are for the handlers of structured properties. $kid is always a property node (a child node of the widget's node).
kdl_boolean
my $flag = $self->kdl_boolean($kid); # 1 or 0
The single argument of a boolean property node: #true and 1 give 1, #false and 0 give 0. Anything else dies, including #null, other numbers and strings such as "false":
Term::Fabulous::Widget::Checkbox: layout property 'checked' must be #true or #false, got 'false'
kdl_border_sides
my $sides = $self->kdl_border_sides($kid); # { top => 1, right => 0, bottom => 1, left => 0 }
The sides of a border property node as a hash reference of all four sides, each 1 or 0: one boolean argument, read like "kdl_boolean", gives every side; key=value pairs whose keys are top, right, bottom and left and whose values are booleans give those sides, and the sides left out are 0. Anything else dies, including an unknown key, a pair whose value is not #true, #false, 1 or 0, and a node with both an argument and pairs.
kdl_value
my $value = $self->kdl_value($kid);
The value of a property node as Perl data: its single argument (title "x" gives 'x', body_indent 2 gives 2, #true gives a true value), or, for a node with only key=value pairs, a hash reference of them (name a=1 b=2 gives { a => 1, b => 2 }). Dies when the node has several arguments, both arguments and pairs, nothing at all, or child nodes.
kdl_argument
my $argument = $self->kdl_argument($kid);
die "title needs a string" unless $argument->is_string;
my $text = $argument->value;
The single argument of a property node as a Text::KDL::XS::Value object, for when you need to check its type. Dies unless the node has exactly one argument and no pairs or children.
kdl_properties
my $props = $self->kdl_properties( $kid, qw(key action) );
The key=value pairs of a property node as a hash reference of Perl values. The listed names are the keys allowed; at least one pair must be present, but not every allowed key. Dies when the node has arguments or child nodes, has no pairs, or has a key that is not allowed (the message lists the allowed keys).
SEE ALSO
Term::Fabulous::Layout, "KDL LAYOUT FILES" in Term::Fabulous::Manual::KDL, "SUBCLASS INTERFACE" in Term::Fabulous::Widget::Box, "WRITING YOUR OWN WIDGETS" in Term::Fabulous::Manual::CustomWidgets, Text::KDL::XS, the example program examples/custom-widget.pl.