package Prima::Spinner; use Prima; use strict; use warnings; our @ISA = qw(Prima::Widget); sub profile_default { my $self = shift; return { %{$self-> SUPER::profile_default}, style => 'drops', active => 1, buffered => 1, value => 50, showPercent => 1, } } sub init { my $self = shift; # Create the timer for the motion of the spinner $self->{timer} = $self->insert( Timer => name => 'Timer', timeout => 200, delegations => ['Tick'], ); # Init the Spinner my %profile = $self-> SUPER::init(@_); $self->{start_angle} = 0; $self-> $_($profile{$_}) for qw( value showPercent style active color hiliteColor); return %profile; } sub Timer_Tick { my $self = shift; my $startang = $self->{start_angle}; #|| 0; if ($self->{style} eq 'drops') { $self->{start_angle} = $startang + 1 if $startang < 7; $self->{start_angle} = 0 if $startang == 7; } elsif ( $self->{style} eq 'circle' || $self->{style} eq 'spiral') { $self->{start_angle} = $startang - 1 if $startang > 0; $self->{start_angle} = 360 if $startang == 0; } $self->update_value; $self->repaint; } sub _pairwise(&$) { my ($sub, $p) = @_; my @p; for ( my $i = 0; $i < @$p; $i += 2 ) { push @p, $sub->($p->[$i], $p->[$i+1]); } return \@p; } sub _scale { my ( $p, $xmul, $ymul) = @_; _pairwise { $xmul * $_[0], $ymul * $_[1] } $p } sub _v_flip($) { _scale(shift, 1, -1) } sub _h_flip($) { _scale(shift, -1, 1) } sub _rotate($) { _pairwise { pop, pop } shift } # Events sub on_paint { my ($self,$canvas) = @_; $canvas->clear; my $x = $self->width/2; my $y = $self->height/2; my $min = ( $x < $y ) ? $x : $y; my $scale_factor = $min / 12; if ($self->{style} eq "circle") { my $color1 = $self->color; my $color2 = $self->hiliteColor; $canvas->color($color2); $canvas->lineWidth(3*$scale_factor); $canvas->ellipse($x, $y, 17.5*$scale_factor,17.5*$scale_factor); $canvas->lineWidth(1.5*$scale_factor); $canvas->color($color1); $canvas->lineEnd(le::Square); $canvas->arc($x, $y, 17.5*$scale_factor,17.5*$scale_factor,$self->{angle1}, $self->{angle2}); if ( $self->{showPercent} ) { my $ref_text = '100%'; my $ext = 10 * $scale_factor; my $attempts = 2; $canvas-> font-> height( 4 * $scale_factor ); while ( $attempts-- ) { my $tw = $canvas-> get_text_width( $ref_text ); last if $tw <= $ext; $canvas-> font-> height( $canvas-> font-> height - $scale_factor ); } if ( $attempts >= 0 ) { $ref_text = int($self-> value + .5) . '%'; my $tw = $canvas-> get_text_width( $ref_text ); $canvas->text_out( $ref_text, $x - $tw/2, $y - $canvas->font->height / 2 ); } } } elsif ( $self->{style} eq 'drops') { my @petal1 = ( 0, 1.5, -2, 7.5, 0, 9.5, 2, 7.5, 0, 1.5 ); my @petal2 = ( 1.5, 1, 3.5, 7, 6.5, 7, 6.5, 4, 1.5, 1 ); $canvas->translate($x, $y); my $stripes = $canvas->new_gradient( palette => [ $self->color, $self->backColor ] )->stripes(8+1); my @colors; for ( my $i = 0; $i < @$stripes; $i+=2 ) { push @colors, $stripes->[$i] for 1 .. $stripes->[$i+1]; } my $fill_spline = sub { my ( $n, $p ) = @_; $canvas->color( $colors[( $self->{start_angle} + $n) % 8] ); $canvas->fill_spline(_scale($p, $scale_factor, $scale_factor)); }; $fill_spline->(7, \@petal2); $fill_spline->(6, _rotate \@petal1); $fill_spline->(5, _v_flip \@petal2 ); $fill_spline->(4, _v_flip \@petal1); $fill_spline->(3, _h_flip _v_flip \@petal2); $fill_spline->(2, _h_flip _rotate \@petal1); $fill_spline->(1, _h_flip \@petal2); $fill_spline->(0, \@petal1 ); } elsif ( $self->{style} eq 'spiral') { my $d1 = 0.8; my $d2 = 0.2; my $dx = $d2 / 4; $canvas->fillWinding(1); $canvas->translate($x, $y); $canvas-> new_path-> scale($scale_factor * 22)-> rotate(-$self->{start_angle})-> arc( 0, 0, $d1 + $dx * 0, $d1 + $dx * 1, 0, 90)-> arc( 0, 0, $d1 + $dx * 2, $d1 + $dx * 1, 90, 180)-> arc( 0, 0, $d1 + $dx * 2, $d1 + $dx * 3, 180, 270)-> arc( 0, 0, $d1 + $dx * 4, $d1 + $dx * 3, 270, 360)-> arc( $d1/2, 0, $d2, $d2, 0, 180)-> arc( 0, 0, $d1 - $dx * 4, $d1 - $dx * 3, 360, 270)-> arc( 0, 0, $d1 - $dx * 2, $d1 - $dx * 3, 270, 180)-> arc( 0, 0, $d1 - $dx * 2, $d1 - $dx * 1, 180, 90)-> arc( 0, 0, $d1 - $dx * 0, $d1 - $dx * 1, 90, 0)-> fill; } } # properties sub style { my ($self, $style) = @_; return $self-> {style} unless $#_; # When the style property is changed, reset the timer frequency # and the start_angle and for style circle the end_angle, too if ( $style eq 'drops') { $self->{timer}->timeout(200); } elsif ( $style eq 'circle' || $style eq 'spiral') { $self->{timer}->timeout(8); } else { Carp::croak("bad style: $style"); } $self->{style} = $style; $self->{start_angle} = 0; $self-> update_value; $self-> repaint; } sub update_value { my $self = shift; $self->{angle1} = (360 + $self->{start_angle} - $self->{value} * 360 / 100) % 360; $self->{angle2} = $self->{start_angle}; $self->{angle2} += 360 if $self->{value} >= 100.0; } sub value { my ( $self, $value ) = @_; return $self->{value} unless $#_; $value = 0 if $value < 0; $value = 100 if $value > 100; $self->{value} = $value; $self-> update_value; $self-> repaint; } sub showPercent { my ( $self, $sp ) = @_; return $self->{showPercent} unless $#_; $self->{showPercent} = $sp; $self-> repaint; } sub active { return $_[0]->{timer}->get_active unless $#_; my ($self, $active) = @_; $active ? $self->{timer}->start : $self->{timer}->stop; } sub start { shift->active(1) } sub stop { shift->active(0) } sub toggle { $_[0]->active(!$_[0]->active) } 1; __END__ =head1 NAME Prima::Spinner - Show a spinner animation =head1 SYNOPSIS use Prima qw(Application Buttons Spinner); my $mw = Prima::MainWindow->new( size => [200, 400], text => 'Button Example'); my $spinner = $mw->insert('Spinner', style => 'drops', size => [200,400], growMode => gm::Center ); my $button = $mw->insert( 'Button', text => 'Start/Stop', checkable => 1, checked => 1, origin => [0,0], onClick => sub { $spinner->toggle }, growMode => gm::XCenter ); run Prima; =head1 DESCRIPTION Prima::Spinner provides a simple spinning animation with two designs and the opportunity to specify the colors of the spinning animation. This is useful to show an indeterminate progress of a running process. =head1 USAGE You can determine the following properties: =head2 Properties =over =item active [BOOLEAN] If now parameter is passed, by this method you can get the active state of the spinning widget. '1' means that the spinner is running, '0' that it is stopped. With C you can start the spinner animation, with C you can stop it. =item color COLOR Inherited from L. C manages the basic foreground color. Concrete for the spinner widget this means the background color of the circle or the color of the active drops. =item hiliteColor COLOR Inherited from L. The color used to draw alternate foreground areas with high contrast. For the spinner widget this means the color of the arc. Only for I style. =item showPercent BOOLEAN If set, displays completion percent as text. Only for I style. =item start Same as C< active(1) > =item stop Same as C< active(0) > =item style STRING C