2022 twenty-four merry days of Perl Feed

A Perlmas Tree

pEFL - 2022-12-08

How to Buy a Christmas Tree on Christmas Day :-S

It's just before Christmas Eve. The air smells of cinnamon and gingerbread, the most beautiful Christmas melodies sound everywhere, the Christmas goose has already been ordered, but wait... A very important detail is missing. Rudolph, the Christmas grouch, did not buy a Christmas tree! And so his wife Fjolla, a Christmas elf, asks him to finally get a tree. The same procedure as every year...

But it's just before Christmas Eve... And so the Christmas tree stand of Knecht Ruprecht is sold out.

A Do-it-Yourself Tree with perl

"No problem", Rudolph thinks. Nowadays you can create a Christmas tree with perl. There was a wonderful module by Dave Cross in the Perl Advent Calendar 2018. But to find the perfect fitting tree, you have to hack the code. Therefore a GUI is needed. And why not to try the new module pEFL?

pEFL is an object oriented binding to the Enlightenment Foundation Libraries. And even if it is a very young module, the Elementary part (the widget toolkit) is already very usable. But let's see:

But before take a deep dive into pEFL we make a global hash for the settings to SVG::ChristmasTree with nice defaults on top of our code:

my %settings = (
    layers => 3,
    width => 1000,
    trunk_length => 100,
    pot_height => 200,
    leaf_colour => 'rgb(9,186,10)',
    bauble_colour => 'rgb(212,175,55)',
    trunk_colour => 'rgb(139,69,19)',
    pot_colour => 'rgb(133,100,69)',
);

All pEFL::Elm applications share the following basic framework, which initializes Elementary, runs the Mainloop and do cleanup when the program exists:

use SVG::ChristmasTree; # for creating the Christmas tree
use pEFL::Elm;
use pEFL::Evas; # for EVAS_CONSTANTS_

pEFL::Elm::init($#ARGV, \@ARGV);
pEFL::Elm::policy_set(ELM_POLICY_QUIT, ELM_POLICY_QUIT_LAST_WINDOW_CLOSED);

### (...GUI CODE COMES HERE...)

pEFL::Elm::run();
pEFL::Elm::shutdown();

Perhaps a little explanation to the policies set with pEFL::Elm::policy_set(): this causes the Mainloop started by pEFL::Elm::run() to quit automatically when the last window of the application is closed.

To create a standard window with a standard background we use the special constructor pEFL::Elm::Win->util_standard_add($name, $title). The autodel setting means that if someone hits the close button, or otherwise uses some normal environment mechanism (keybinding, menu) to close the window, the window object is automatically deleted on the application side. If you don't do this, these mechanisms will not work and the window will stay around if you do not manually add a callback to handle "delete, request" events.

my $win = pEFL::Elm::Win->util_standard_add("christmas-tree", "Christmas Tree");
$win->autodel_set(1);
$win->resize(800,800);

The content will be framed in a pEFL::Elm::Box. This basic container of Elementary is just a linear method of arranging widgets horizontally or vertically. Attention should be paid to the functions $widget->size_hint_weight|align_set(x_expand|align, y_expand_align). With these you control hints for expanding and aligning of the widget packed to the container box. Possible arguments are 0.0 (no expand/align to left/top) to 1.0 (expand/align to the right/bottom). If you want the widget to take up all the space in its parent you can use the special constants EVAS_HINT_EXPAND and EVAS_HINT_FILL together. Because it is much paperwork, we create a function for widgets that expand and fill in all directions:

my $frame = pEFL::Elm::Frame->add($win);
_expand_widget($frame);
my $container = pEFL::Elm::Box->add($win);
_expand_widget($container);
$frame->style_set("pad_large");
$frame->content_set($container);
$frame->show(); $win->resize_object_add($frame);

[...]
    
sub _expand_widget {
    my ($widget) = @_;
    $widget->size_hint_weight_set(EVAS_HINT_EXPAND,EVAS_HINT_EXPAND);
    $widget->size_hint_align_set(EVAS_HINT_FILL,EVAS_HINT_FILL);
}

In the container box we pack three widgets:

1. An pEFL::Elm::Image widget that serves as a viewer
my $viewer = pEFL::Elm::Image->add($container);
_expand_widget($viewer);
$viewer->show(); $container->pack_end($viewer);
2. An pEFL::Elm::Table widget where the controls of the several settings are aligned

A table is like a box with 2 dimensions. An item of the table can span multiple columns and rows, and even overlap with other items.

my $table = pEFL::Elm::Table->add($container);
$table->padding_set(10,10);
$table->size_hint_weight_set(EVAS_HINT_EXPAND,0); # Expand and fill only to the
$table->size_hint_align_set(EVAS_HINT_FILL,0); # X-Direction
$table->show(); $container->pack_end($table);

For changing the settings of the tree we add several sliders.

_add_slider_setting($table, 4, {label => "Width of the tree", min => 700, max => 3000, key => "width"});
_add_slider_setting($table, 5, {label => "Layers", min => 2, max => 8, key => "layers"});
_add_slider_setting($table, 6, {label => "Trunk length", min => 50, max => 300, key => "trunk_length"});
_add_slider_setting($table, 7, {label => "Pot height", min => 100, max => 400, key => "pot_height"});

For this we use the custom function _add_slider_settings($table,$row,$option_hash):

sub _add_slider_setting {
    my ($table,$row,$opts) = @_;

    my $label = pEFL::Elm::Label->add($table);
    $label->text_set($opts->{label});
    $label->show(); $table->pack($label,0,$row,1,1);

    my $spinner = pEFL::Elm::Slider->add($table);
    $spinner->size_hint_align_set(EVAS_HINT_FILL,0.5);
    $spinner->size_hint_weight_set(EVAS_HINT_EXPAND,0.0);
    $spinner->min_max_set($opts->{min},$opts->{max});
    $spinner->step_set(1);
    $spinner->indicator_format_set("%1.0f");
    $spinner->value_set($settings{$opts->{key}});
    $spinner->show(); $table->pack($spinner,1,$row,2,1);

    $spinner->smart_callback_add("delay,changed" => sub {$settings{$opts->{key}} = int($_[1]->value_get());}, undef);
}

Most of the code should be self-explanatory. Therefore only two little explanations:

With $spinner->indicator_format_set("%1.0f"); the popup for the actual value shows only integers.

Very important is the $spinner->smart_callback_add("eventName",\&eventSub,$data). With this you can register an event handler for smart callbacks, the most often used event types in EFL. The callback function takes as parameters the additional $data defined in the $widget->smart_callback_add(), the object to which the event happened, and the event info data, which depends on the object type and the event at play. So the signature is

sub smart_event_cb {
    my ($data, $obj, $evInfo) = @_;
}

In the example code we use an anonymous subroutine as event handler that sets the key in the setting hash with the actual value of the spinner object.

3. Last we add the button to create the Christmas tree to the container-box

and show the window (don't forget this!!)

my $btn = pEFL::Elm::Button->add($container);
$btn->text_set("Create a new Christmas Tree");
$btn->smart_callback_add("clicked",\&create_christmas_tree,$viewer);
$btn->show(); $container->pack_end($btn);

$win->show()

Again we register a callback to the following event handler that creates the SVG file and let the viewer show the created tree. A little hack is needed with $viewer->file_set(undef,""). Otherwise the viewer doesn't know that the file has changed:

sub create_christmas_tree {
    my ($viewer,$obj,$evinfo) = @_;
        
    my $tree = SVG::ChristmasTree->new(\%settings);

    open my $fh, ">", "tree.svg" or die "Could not write to ./tree.svg: $!\n";
    print $fh $tree->as_xml;
    close $fh;

    $viewer->file_set(undef,"");
    $viewer->file_set("./tree.svg","");
}

Conclusion

I hope you enjoyed this little introduction to pEFL. Because the Perl advent articles should be light and trivial, the explained version only gives you control over the numeric settings of SVG::ChristmasTree. But at my Perl Advent 2022 github repository you find also an advanced version where you can also alter the colors of the tree, baubles and pot. The additions adjust the colorselector examples under examples/colorselector.pl in the pEFL distribution and hopefully are understandable.

If you want to get deep into programming with pEFL, see the explanations at pEFL. As the API of pEFL is (apart from its object-oriented interface) deliberately kept close to the Elementary C API, the documentations at the Enlightenment docs should help you, too. The Perl method names usually remove the prefix at the beginning of the C functions. Therefore applying the C documentation should be no problem.

And of course, you can get help at Github. And perhaps you want to contribute and to make the pEFL bindings better? In any case there is much to do :-)

Merry Christmas 🎄 🎄 🎄 🎄 🎄 🎄

Gravatar Image This article contributed by: Maximilian Lika <perlmax@cpan.org>