Alt-Tickit-Widgets-ObjectPad

 view release on metacpan or  search on metacpan

lib/Tickit/Widget.pm  view on Meta::CPAN

#  You may distribute under the terms of either the GNU General Public License
#  or the Artistic License (the same terms as Perl itself)
#
#  (C) Paul Evans, 2009-2018 -- leonerd@leonerd.org.uk

package Tickit::Widget;

use strict;
use warnings;

our $VERSION = '0.53';

use Carp;
use Scalar::Util qw( weaken );
use List::Util 1.33 qw( all );

use Tickit::Pen;
use Tickit::Style;
use Tickit::Utils qw( textwidth );
use Tickit::Window 0.57;  # $win->bind_event
use Tickit::Event 0.63;  # $info->type("newapi") on Focus

use constant PEN_ATTR_MAP => { map { $_ => 1 } @Tickit::Pen::ALL_ATTRS };

use constant KEYPRESSES_FROM_STYLE => 0;

use constant CAN_FOCUS => 0;

=head1 NAME

C<Tickit::Widget> - abstract base class for on-screen widgets

=head1 DESCRIPTION

This class acts as an abstract base class for on-screen widget objects. It
provides the lower-level machinery required by most or all widget types.

Objects cannot be directly constructed in this class. Instead, a subclass of
this class which provides a suitable implementation of the C<render_to_rb> and
other provided methods is derived. Instances in that class are then
constructed.

See the C<EXAMPLES> section below.

The core F<Tickit> distribution only contains a couple of simple widget
classes. Many more widget types are available on CPAN. Almost certainly for
any widget-based program you will want to at least install the
L<Tickit::Widgets> distribution, which provides many of the basic UI types of
widget.

=head1 STYLE

The following style tags are used on all widget classes that use Style:

=over 4

=item :focus

Set when this widget has the input focus

=back

The following style actions are used:

=over 4

=item focus_next_before (<Tab>)

=item focus_next_after (<S-Tab>)

Requests the focus move to the next or previous focusable widget in display
order.

=back

lib/Tickit/Widget.pm  view on Meta::CPAN

      $self->reshape;
      $self->redraw;
   }
   elsif( keys %changed_pens or $redraw ) {
      $self->redraw;
   }
}

=head2 set_window

   $widget->set_window( $window )

Sets the L<Tickit::Window> for the widget to draw on. Setting C<undef> removes
the window.

If a window is associated to the widget, that window's pen is set to the
current widget pen. The widget is then drawn to the window by calling the
C<render_to_rb> method. If a window is removed (by setting C<undef>) then no
cleanup of the window is performed; the new owner of the window is expected to
do this.

This method may invoke the C<window_gained> and C<window_lost> methods.

=cut

sub set_window
{
   my $self = shift;
   my ( $window ) = @_;

   # Early out if no change
   return if !$window and !$self->window;
   return if $window and $self->window and $self->window == $window;

   if( $self->{window} and !$window ) {
      $self->{window}->set_pen( undef );
      $self->window_lost( $self->{window} );
   }

   $self->{window} = $window;

   if( $window ) {
      $window->set_pen( $self->{pen} );

      $self->window_gained( $self->{window} );

      $window->take_focus if delete $self->{focus_pending};

      $self->reshape;

      $window->expose;
   }
}

sub window_gained
{
   my $self = shift;

   my $window = $self->window;

   weaken $self;

   my $event_ids = $self->{event_ids} //= {};

   $event_ids->{geomchange} = $window->bind_event( geomchange => sub {
      $self->reshape;
      $self->redraw if !$self->parent;
   } );

   $event_ids->{expose} = $window->bind_event( expose => sub {
      my ( $win, undef, $info ) = @_;
      $win->is_visible or return;

      $info->rb->setpen( $self->{pen} );

      $self->render_to_rb( $info->rb, $info->rect );
   });

   $event_ids->{focus} = $window->bind_event( focus => sub {
      my ( $win, undef, $info ) = @_;
      $self->_on_win_focus( $win, $info->type( "newapi" ), $info->win );
   } ) if $self->can( "_widget_style_type" );

   if( $self->can( "on_key" ) or $self->KEYPRESSES_FROM_STYLE ) {
      $event_ids->{key} = $window->bind_event( key => sub {
         my ( $win, undef, $info ) = @_;

         {
            # Space comes as " " but we'd prefer to use "Space" in styles
            my $keystr = $info->str eq " " ? "Space" : $info->str;

            my $action;
            $action = $self->get_style_values( "<$keystr>" ) if $self->KEYPRESSES_FROM_STYLE;
            $action //= "focus_next_after"  if $keystr eq "Tab";
            $action //= "focus_next_before" if $keystr eq "S-Tab";

            last unless $action;

            my $code = $self->can( "key_$action" );
            return 1 if $code and $code->( $self, $info );
         }
         my $code = $self->can( "on_key" );
         return 1 if $code and $code->( $self, $info );
      } );
   }

   $event_ids->{mouse} = $window->bind_event( mouse => sub {
      my ( $win, undef, $info ) = @_;
      $self->take_focus if $self->CAN_FOCUS and $info->button == 1 and $info->type eq "press";
      $self->on_mouse( $info ) if $self->can( "on_mouse" );
   } );
}

sub _on_win_focus
{
   my $self = shift;
   my ( $win, $focus ) = @_;

   $self->set_style_tag( focus => $focus eq "in" );
}

sub key_focus_next_after
{
   my $self = shift;
   $self->parent and $self->parent->focus_next( after => $self );
   return 1;
}

sub key_focus_next_before
{
   my $self = shift;
   $self->parent and $self->parent->focus_next( before => $self );
   return 1;
}

sub window_lost
{
   my $self = shift;

   my $window = $self->window;

   $window->unbind_event_id( $_ ) for values %{ $self->{event_ids} };
}

=head2 window

   $window = $widget->window

Returns the current window of the widget, if one has been set using
C<set_window>.

=cut

sub window
{
   my $self = shift;
   return $self->{window};
}

=head2 set_parent

   $widget->set_parent( $parent )

Sets the parent widget; pass C<undef> to remove the parent.

C<$parent>, if defined, must be a subclass of L<Tickit::ContainerWidget>.

=cut

sub set_parent
{
   my $self = shift;
   my ( $parent ) = @_;

   !$parent or $parent->isa( "Tickit::ContainerWidget" ) or croak "Parent must be a ContainerWidget";

   weaken( $self->{parent} = $parent );
}

=head2 parent

   $parent = $widget->parent

Returns the current container widget

=cut

sub parent
{
   my $self = shift;
   return $self->{parent};
}

=head2 resized

   $widget->resized

Provided for subclasses to call when their size requirements have or may have
changed. Re-calculates the size requirements by calling C<lines> and C<cols>
again, then calls C<set_requested_size>.

=cut

sub resized
{
   my $self = shift;
   # 'scalar' just in case of odd behaviour in subclasses
   $self->set_requested_size( scalar $self->lines, scalar $self->cols );
}

=head2 set_requested_size

   $widget->set_requested_size( $lines, $cols )

Provided for subclasses to call when their size requirements have or may have
changed. Informs the parent that the widget requires a differently-sized
window if the dimensions are now different to last time.

=cut

sub set_requested_size
{
   my $self = shift;
   my ( $new_lines, $new_cols ) = @_;

   return if defined $self->{req_lines} and $self->{req_lines} == $new_lines and
             defined $self->{req_cols}  and $self->{req_cols}  == $new_cols;

   $self->{req_lines} = $new_lines;
   $self->{req_cols}  = $new_cols;

   if( $self->parent ) {
      $self->parent->child_resized( $self );
   }
   else {
      $self->reshape if $self->window;
      $self->redraw;



( run in 2.099 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )