package ButtonCollection; use strict; use warnings; use Tk::Zinc::Graphics; use Carp; use base qw{ Class::Accessor::Fast }; __PACKAGE__->mk_accessors(qw{ spacing order }); __PACKAGE__->mk_ro_accessors(qw{ width height }); # Initialization and create a (zinc) group for our buttons. sub new { my ($proto, $zinc, $args) = @_; my $type = ref($proto) || $proto; my $self = { 'zinc' => $zinc, 'bbox' => $args->{'bbox'} || [[0,0],[200,200]], 'packer' => $args->{'packer'} || 'vertical', 'spacing' => defined($args->{'spacing'}) ? $args->{'spacing'} : 5, 'buttons' => {}, 'order' => [], }; $self->{'width'} = $self->{'bbox'}->[1][0] - $self->{'bbox'}->[0][0]; $self->{'height'} = $self->{'bbox'}->[1][1] - $self->{'bbox'}->[0][1]; $self->{'group'} = $self->{'zinc'}->add( 'group', 1, -atomic => 0, -visible => 0, -tags => ['buttoncollection'], ); # Place group at bbox X1,Y1, to ease positioning buttons later on. $self->{'zinc'}->translate( $self->{'group'}, $self->{'bbox'}[0][0], $self->{'bbox'}[0][1] ); return bless($self, $type); } # # Given type, name and callback; create a group, shape and text item on the # zinc canvas, representing a button. No scaling/positioning is done at this # time (but mouse bindings are..) # # !! NOTE: 'type' is magic and requires two gradients to be present in # self->zinc. They must be named "$type" and "active_$type". # gradient "active_$type" is used when pointer is over button. # sub add_button { my ($self, $type, $name, $callback) = @_; croak "Must specify name." unless defined $name; croak "Must specify type." unless defined $type; my $group = $self->{'zinc'}->add( 'group', $self->{'group'}, -atomic => 1, -visible => 0, -tags => ['button', $type, 'group'], ); my $shape = $self->{'zinc'}->add( 'curve', $group, $self->{'bbox'}, -tags => ['button', $type, 'shape'], -fillcolor => $type, -filled => 1, -closed => 1, -linewidth => 1, -linecolor => '#ffffff', ); my $text = $self->{'zinc'}->add( 'text', $group, -color => '#ffffff', -anchor => 'center', -text => $name, -tags => ['button', $type, 'text'], ); if (defined($callback)) { $self->{'zinc'}->bind($group, '<1>', \&{$callback}); } $self->{'zinc'}->bind($group, '', sub { $self->{'zinc'}->itemconfigure($shape, -fillcolor=>'active_'.$type); }); $self->{'zinc'}->bind($group, '', sub { $self->{'zinc'}->itemconfigure($shape, -fillcolor=>$type); }); $self->{'buttons'}{$group} = [$shape, $text]; push @{ $self->{'order'} }, $group; return $group; } # Return a buttons shape and text items sub get_button { my ($self, $group) = @_; if (defined $self->{'buttons'}{$group}) { return @{ $self->{'buttons'}{$group}}; } croak "unknown button $group\n"; } # Given nothing, reshape all the (ordered) buttons in the collection # to fit within self->bbox according to packer (then make group visible) sub pack { my ($self) = @_; my $numbtn = 1 + scalar @{ $self->{'order'} }; # Get the width, height and shape of ONE button. my ($BW, $BH) = $self->_get_button_size($numbtn); my $shapecoords = &roundedRectangleCoords( [ [-(int $BW/2),-(int $BH/2)], [int $BW/2, int $BH/2] ] ); # First hide and deactivate all buttons foreach my $group ( keys %{ $self->{'buttons'} } ) { $self->{'zinc'}->itemconfigure($group, -visible => 0); $self->{'zinc'}->itemconfigure($group, -sensitive => 0); } # Then resize, move, show and activate the ones specified by ->order my $i=0; my ($group, $shape, $text, $x, $y); foreach my $group (@{ $self->{'order'} }) { ($shape, $text) = @{ $self->{'buttons'}{$group} }; ($x, $y) = $self->_get_button_pos($BW, $BH, $i); $self->{'zinc'}->coords($shape, $shapecoords); $self->{'zinc'}->treset($group); $self->{'zinc'}->translate($group, $x, $y); $self->{'zinc'}->itemconfigure($group, -visible => 1); $self->{'zinc'}->itemconfigure($group, -sensitive => 1); $i++; } $self->{'zinc'}->itemconfigure($self->{'group'}, -visible => 1); } # Accessor/mutator sub packer { my ($self, $packer) = @_; if (defined $packer and $packer =~ m/^(horizontal|vertical)$/) { $self->{'packer'} = $packer; } elsif (not defined $packer) { return $self->{'packer'}; } else { die "Packer must be horizontal or vertical."; } } # Accessor/mutator sub bbox { my ($self, $bbox) = @_; if (defined $bbox) { $self->{'bbox'} = $bbox; $self->{'width'} = $self->{'bbox'}->[1][0] - $self->{'bbox'}->[0][0]; $self->{'height'} = $self->{'bbox'}->[1][1] - $self->{'bbox'}->[0][1]; return 1; } return $self->{'bbox'}; } # Accessor, return zinc groups of all buttons sub buttons { my $self = shift; return keys %{ $self->{buttons} }; } # Accessor, return whether _collection_ is visible sub visible { my $self = shift; return $self->{'zinc'}->itemcget($self->{'group'}, -visible); } # hides the collection('s zinc group) sub hide { my $self = shift; $self->{'zinc'}->itemconfigure($self->{'group'}, -visible => 0); } # shows the collection('s zinc group) sub show { my $self = shift; $self->{'zinc'}->itemconfigure($self->{'group'}, -visible => 1); } # _get_button_pos; # Given button width/height and number, return the buttons x/y position sub _get_button_pos { my ($self, $BW, $BH, $num) = @_; my ($x, $y); if ($self->{'packer'} eq 'horizontal') { $x = ($BW/2)+($num*($self->spacing+$BW)+$self->spacing); $y = ($BH/2)+($self->spacing); } elsif ($self->{'packer'} eq 'vertical') { $x = ($BW/2)+($self->spacing); $y = ($BH/2)+($num*($self->spacing+$BH)+$self->spacing); } else { croak "Unsupported packer:", $self->{packer}; } # print "returning button $num position: $x, $y\n"; return ($x, $y); } # _get_button_size: # Given number of buttons in collection, return width and height of one button sub _get_button_size { my ($self, $numbtn) = @_; my ($BW, $BH); $numbtn -= 1; croak "pack() with no buttons?" if ($numbtn <= 0); if ($self -> {'packer'} eq 'horizontal') { $BW = ($self->{'width'} - ((1+$numbtn)*$self->{spacing})) / $numbtn; $BH = $self->{'height'} - ($self->{spacing}*2); } elsif ($self->{'packer'} eq 'vertical') { $BW = $self->{'width'} - ($self->{spacing}*2); $BH = ($self->{'height'} - $self->spacing-($numbtn*$self->{spacing}) ) / $numbtn; } else { croak "Unsupported packer:", $self->{packer}; } return ($BW, $BH); } 1;