package HexGrid::Tile; use v5.30; use Moo; use MooX::Aliases; use Carp; use HexGrid::Pin; use Data::Dumper; use feature "signatures"; no warnings "experimental::signatures"; has nw => (is => 'ro', required => 1); has sw => (is => 'ro', required => 1); has style => (is => 'rw', default => sub { {} }); has colour => (is => 'rw', alias => 'color'); has opacity => (is => 'rw'); has images => (is => 'rw', alias => [qw(image background backgrounds)]); has show_coords => (is => 'rw'); has coords_colour => (is => 'rw', default => 'white'); has css_class => (is => 'rw'); has id_suffix => (is => 'rw', default => '_tile'); my %docks = #values are fractions of the height/width ( sw => { order => 1, x => 0, y => 0.5, w => 0.5, h => 0.5 }, se => { order => 2, x => 0.5, y => 0.5, w => 0.5, h => 0.5 }, n => { order => 3, x => 0.25, y => 0, w => 0.5, h => 0.5 }, ); # By default makes all pin docks exist but unfilled has pins => (is => 'rw', default => sub { +{ map {$_ => undef} keys %docks } }); # Class # Convenience factory method sub at($nw,$sw,@rest) { return HexGrid::Tile->new(nw => $nw, sw => $sw, @rest); } # Returns the sequence of points making up an equilateral hexagon inscribed in a $w x $h rectangle sub hex_path($w, $h) { # More readable for me at least as a sequence of points than as two sequences my @points = ([0, $h/2], [$w*1/4, 0], [$w*3/4, 0], [$w, $h/2], [$w*3/4, $h], [$w*1/4, $h]); # Join pairs with a comma, and sequence with spaces return join " ", (map {join ",", @$_} @points); } # Instance sub pin($this, $pin, $dock = undef) { if(ref $pin eq 'HexGrid::Pin') { $this->_do_pin($pin, $dock); } elsif(ref $pin eq 'ARRAY') { map $this->_do_pin($_), @$pin; } elsif(ref $pin eq 'HASH') { foreach my $dock (keys %$pin) { (carp("dock $dock doesn't exist") && return) unless grep { $_ eq $dock } keys %docks; $this->{pins}{$dock} = $pin->{$dock}; } return; } else { carp ("bad pin type") && return; } } sub _do_pin($this, $pin, $dock = undef) { if($dock) { (carp("bad dock") && return) unless $dock =~ /^sw|se|n$/i; $this->{pins}{$dock} = $pin; } else { # If no dock was specified, find the first available according to the order above my $success = 0; DOCK: foreach my $dock (sort { $docks{$a}->{order} <=> $docks{$b}->{order} } (keys %docks)) { unless(defined $this->{pins}{$dock}) { $this->{pins}{$dock} = $pin; $success = 1; last DOCK; } } (carp("no free dock") && return) unless $success; } } sub render($this, $container, $width, $height, $laters = undef) { my $g = $container->g ( id => HexGrid::to_id("$this->{nw}_$this->{sw}$this->{id_suffix}"), ); my $hex = $g->polygon(points => hex_path($width, $height), id => "$this->{nw}_$this->{sw}_inner_hex", style => $this->style); # Have tile contents clip to the hexagon my $clipPath = $g->clipPath(id => "$this->{nw}_$this->{sw}_clip"); $clipPath->use(href => "#$hex->{id}"); if($this->{colour}) { $hex->{fill} = $this->{colour}; } if(defined($this->{images})) { # This allows for the image/images property to be single- or multi-valued $this->{images} = [$this->{images}] if ref($this->{images}) ne 'ARRAY'; foreach my $image (@{$this->{images}}) { # The actual image is defined at the HexGrid level, here we reference it my $use_element = $g->use(id => "$this->{nw}_$this->{sw}_${image}_use", href => "#${image}_symbol", width => $width, height => $height, "clip-path" => "url(#$clipPath->{id})"); } } if(defined($this->{opacity})) { $hex->{'fill-opacity'} = $this->{opacity}; } if($this->{show_coords}) { #TODO: Should probably parametrize these numbers, particularly font-size my $text = $g->text(x => "@{[0.1 * $width]}", y => "@{[0.6 * $height]}", class => 'coords'); $text->{'font-size'} = '3em'; $text->{fill} = $this->{coords_colour}; $text->cdata("$this->{nw},$this->{sw}"); } if(defined $this->{css_class}) { $g->{class} = $this->{css_class} } if(defined $this->{pins}) { foreach my $key (keys $this->{pins}->%*) { next unless defined $this->{pins}{$key}; my $x = $width * $docks{$key}->{x}; my $w = $width * $docks{$key}->{w}; my $y = $height * $docks{$key}->{y}; my $h = $height * $docks{$key}->{h}; my $pin_element = $this->{pins}{$key}->render($g, $x, $y, $w, $h, $laters); $pin_element->{"clip-path"} = "url(#$clipPath->{id})"; } } return $g; } 1;