Created
November 30, 2012 09:38
-
-
Save cedric-vincent/4174770 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
=begin POD | |
=head1 Whatever the layout manager is | |
=head2 Introduction | |
This article aims to demonstrate how C<Whatever> -- one of the many | |
interesting Perl 6 curiosities -- could be useful to easily implement | |
and use complex things like a layout manager. In a couple of words, a | |
layout manager is the part of a graphical interface in charge of the | |
spatial arrangement of objects like windows or widgets. For the sake | |
of simplicity, the layout manager implemented in this article will | |
comply with the following three rules: | |
=over | |
=item * | |
there are only two kinds of widgets: terminal or container, the latter | |
contains both kinds; | |
=item * | |
a widget cannot be overlapped, except for containers which fully | |
contain their sub-widgets; and | |
=item * | |
only the height can be adjusted, this size can be either static, | |
dynamic, or intentionally left unspecified. | |
=back | |
=head2 Usage | |
From the user's point-of-view, this layout manager aims to be as easy | |
to use as possible. For example, it shouldn't be hard to specify such | |
typical interface below, inspired from a text-based program. In this | |
example, the I<interface> and I<body> widgets are containers, all the | |
others are terminals: | |
interface (X lines) | |
+----> +------------------------------------------+ | |
| | menu bar (1 line) | body (remaining space) | |
| +------------------------------------------+ <----+ | |
| | subpart 1 (1/3 of the remaining space) | | | |
| | | | | |
| | | | | |
| +------------------------------------------+ | | |
| | subpart 2 (remaining space) | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| +------------------------------------------+ <----+ | |
| | status bar (1 line) | | |
+----> +------------------------------------------+ | |
The user don't know what the I<remaining space> is in advance because | |
such an interface is arbitrary resizable. As a consequence it should | |
be specified as a non-predefined value; this is where C<*> -- the | |
<Whatever> object -- comes in handy. This object is interesting for | |
two reasons: | |
=over | |
=item * | |
from the user's point-of-view, the definition of non-static sizes is | |
as simple as: C<* / 3> for the I<subpart 1> (dynamic) and just C<*> | |
for the I<subpart 2> (unspecified); and | |
=item * | |
from the developer's point-of-view, Perl 6 transforms automatically | |
things like C<$size = * / 3> into a closure: C<<x -> x / 3>>. Then, | |
it could be called like a regular function: C<$size($x)>. | |
=back | |
That way, the previous GUI can be transliterated into the following | |
lines of code: | |
my $interface = | |
Widget.new(name => 'interface', size => $x, sub-widgets => ( | |
Widget.new(name => 'menu bar', size => 1), | |
Widget.new(name => 'main part', size => *, sub-widgets => ( | |
Widget.new(name => 'subpart 1', size => * / 3), | |
Widget.new(name => 'subpart 2', size => *))), | |
Widget.new(name => 'status bar', size => 1))); | |
=head2 Implementation | |
The drawing of terminal widgets is straightforward since most of the | |
work is done by containers. Those are in charge to compute the | |
I<remaining space> as well as to uniformly distribute widgets that | |
have an I<unspecified> size: | |
=end POD | |
class Widget { | |
has $.name; | |
has $.size is rw; | |
has Widget @.sub-widgets; | |
method compute-layout($remaining-space? is copy, $unspecified-size? is copy) { | |
$remaining-space //= $!size; | |
if @!sub-widgets == 0 { # Terminal | |
my $computed-size; | |
given $!size { | |
when Real { $computed-size = $_ } | |
when Callable { $computed-size = .($remaining-space) } | |
when Whatever { $computed-size = $unspecified-size } | |
} | |
self.draw($computed-size); | |
} | |
else { # Container | |
my @static-sizes = grep Real, @!sub-widgets».size; | |
my @dynamic-sizes = grep Callable, @!sub-widgets».size; | |
my $nb-unspecified = +grep Whatever, @!sub-widgets».size; | |
$remaining-space -= [+] @static-sizes; | |
$unspecified-size = ([-] $remaining-space, @dynamic-sizes».($remaining-space)) | |
/ $nb-unspecified; | |
.compute-layout($remaining-space, $unspecified-size) for @!sub-widgets; | |
} | |
} | |
method draw(Real $size is copy) { | |
"+{'-' x 25}+".say; | |
"$!name ($size lines)".fmt("| %-23s |").say; | |
"|{' ' x 25}|".say while --$size > 0; | |
} | |
} | |
=begin POD | |
Here, any C<Callable> object can be used to specify a dynamic size, as | |
far as it takes the computed I<remaining space> as argument. That | |
means it is possible to specify more sophisticated dynamic size by | |
passing a code C<Block>. For example, C<{ max(5, $^x / 3) }> ensures | |
the widget has a proportional size that can't decrease below C<5>. | |
=head2 Conclusion | |
It's time to check if this trivial layout manager works correctly both | |
in Rakudo and Niecza, the two most advanced implementations of Perl 6. | |
The following test is rather simple, it creates and draws an | |
interface, then resize it and draws it again: | |
=end POD | |
my $interface = | |
Widget.new(name => 'interface', size => 11, sub-widgets => ( | |
Widget.new(name => 'menu bar', size => 1), | |
Widget.new(name => 'main part', size => *, sub-widgets => ( | |
Widget.new(name => 'subpart 1', size => * / 3), | |
Widget.new(name => 'subpart 2', size => *))), | |
Widget.new(name => 'status bar', size => 1))); | |
$interface.compute-layout; # Draw | |
$interface.size += 3; # Resize | |
$interface.compute-layout; # Redraw | |
=begin POD | |
The results before and after resizing are respectively displayed | |
below. They are close enough from the initial mockup, n'est-ce pas? | |
+-------------------------+ +-------------------------+ | |
| menu bar (1 lines) | | menu bar (1 lines) | | |
+-------------------------+ +-------------------------+ | |
| subpart 1 (3 lines) | | subpart 1 (4 lines) | | |
| | | | | |
| | | | | |
+-------------------------+ | | | |
| subpart 2 (6 lines) | +-------------------------+ | |
| | | subpart 2 (8 lines) | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
+-------------------------+ | | | |
| status bar (1 lines) | | | | |
| | | |
+-------------------------+ | |
| status bar (1 lines) | | |
Finally, the implementation of such a I<flexible> program is really | |
simple in Perl 6: everything is already there, in the core language. | |
Obviously, this trivial layout manager isn't ready for prime-time | |
since a lot of things are missing: sanity checks, multiple dimensions, | |
... but those are left as exercises to you, the reader ;) For any | |
questions or comments, feel free to meet Perl 6 fellows on IRC (#perl6 | |
on freenode). | |
=head2 Bonus | |
As seen previously, C<$!size> can be C<Whatever>, but it shouldn't be | |
I<whatever>. For example, a negative C<Real> or a string are not | |
correct values. Once again Perl 6 provides a simple yet powerful | |
feature: I<constrained> types. In a couple of words this permits to | |
define new types from a set of constraints: | |
subset PosReal of Real where * >=0; | |
subset Size where { .does(PosReal) | |
or .does(Callable) and .signature ~~ :(PosReal --> PosReal) | |
or .does(Whatever) }; | |
has Size $.size is rw; | |
=end POD |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment