Acme-POE-Tree

 view release on metacpan or  search on metacpan

lib/Acme/POE/Tree.pm  view on Meta::CPAN

	my ($class, $arg) = @_;

	my $self = bless { %{$arg || {}} }, $class;

	$self->{light_delay} ||= 1;
	$self->{star_delay}  ||= 1.33;

	POE::Session->create(
		object_states => [
			$self => {
				_start        => "_setup_tree",
				got_keystroke => "_handle_keystroke",
				got_sigwinch  => "_handle_sigwinch",
				paint_tree    => "_paint_tree",
				light_cycle   => "_cycle_lights",
				star_cycle    => "_cycle_star",
				shut_down     => "_handle_shut_down",
			},
		],
	);

	return $self;
}

sub run {
	my $self = shift;
	POE::Kernel->run();
}

sub _setup_tree {
	my ($self, $kernel, $heap) = @_[OBJECT, KERNEL, HEAP];

	# Tell this session about terminal size changes.
	$kernel->sig(WINCH => "got_sigwinch");

	# Set up Curses, and notify this session when there's input.
	$heap->{console} = POE::Wheel::Curses->new(
		InputEvent => 'got_keystroke',
	);

	# Initialize the tree's color palette.
	my @light_colors = (
		COLOR_BLUE, COLOR_YELLOW, COLOR_RED, COLOR_GREEN, COLOR_MAGENTA
	);

	init_pair($_, $light_colors[$_-1], COLOR_BLACK) for 1..@light_colors;
	$heap->{light_colors} = [ map { COLOR_PAIR($_) } (1..@light_colors) ];

	init_pair(@light_colors + 2, COLOR_GREEN, COLOR_BLACK);
	$heap->{color_tree} = COLOR_PAIR(@light_colors + 2) | A_DIM;

	init_pair(@light_colors + 3, COLOR_WHITE, COLOR_BLACK);
	$heap->{color_bg} = COLOR_PAIR(@light_colors + 3);

	init_pair(@light_colors + 4, COLOR_YELLOW, COLOR_BLACK);
	$heap->{color_star} = COLOR_PAIR(@light_colors + 4);

	# Start the star cycle.
	$heap->{star_cycle} = 0;

	# Start the star and light timers.
	$kernel->delay("light_cycle", $self->{light_delay});
	$kernel->delay("star_cycle", $self->{star_delay});

	# Run until an automatic cutoff time has elapsed.
	$kernel->delay("shut_down", $self->{run_for}) if $self->{run_for};

	# Cause the tree to be painted.
	$kernel->yield("paint_tree");
}

# Some window managers send a lot of window-change signals during a
# window resize.  This waits for the user to let go before finally
# painting the new tree.

sub _handle_sigwinch {
	$_[KERNEL]->delay(paint_tree => 0.5);
}

# Handle keystrokes.  Quit if the user presses "q".

sub _handle_keystroke {
	my $keystroke = $_[ARG0];

	# Make control and extended keystrokes printable.
	if ($keystroke lt ' ') {
		$keystroke = '<' . uc(unctrl($keystroke)) . '>';
	}
	elsif ($keystroke =~ /^\d{2,}$/) {
		$keystroke = '<' . uc(keyname($keystroke)) . '>';
	}

	if ( $keystroke eq '<^C>' or $keystroke eq 'q') {
		$_[KERNEL]->yield("shut_down");
	}
}

# Repaint the tree.  This happens after every terminal resize.

sub _paint_tree {
	my $heap = $_[HEAP];
	$heap->{lights} = grow_tree($heap);
}

# Periodically change which lights are lit.

sub _cycle_lights {
	my ($self, $kernel, $heap) = @_[OBJECT, KERNEL, HEAP];

	if (CYCLE_TYPE eq "random") {
		foreach my $light (@{$heap->{lights}}) {
			next unless rand() < 0.25;

			$light->{lit} = !$light->{lit};
			$light->{c_paint} = $light->{c_main} | ($light->{lit} ? A_BOLD : A_DIM);

			if ($light->{lit} or DIM_BULBS) {
				attrset($light->{c_paint});
				addstr($light->{y}, $light->{x}, "o");
			}
			else {
				addstr($light->{y}, $light->{x}, " ");
			}
		}
	}
	elsif (CYCLE_TYPE eq "cycle") {
		foreach my $light (@{$heap->{lights}}) {
			$light->{lit} = (
				$light->{c_main} == $heap->{light_colors}[$heap->{light_cycle} || 0]
			) || 0;
			$light->{c_paint} = $light->{c_main} | ($light->{lit} ? A_BOLD : A_DIM);

			if ($light->{lit} or DIM_BULBS) {
				attrset($light->{c_paint});
				addstr($light->{y}, $light->{x}, "o");
			}
			else {
				addstr($light->{y}, $light->{x}, " ");
			}
		}

		$heap->{light_cycle}++;
		$heap->{light_cycle} = 0 if (
			$heap->{light_cycle} >= @{$heap->{light_colors}}
		);
	}

	do_refresh($heap);

	$kernel->delay("light_cycle", $self->{light_delay});
}

# The star periodically shimmers.

sub _cycle_star {
	my ($self, $kernel, $heap) = @_[OBJECT, KERNEL, HEAP];

	$heap->{star_cycle}++;
	draw_star($heap);

	do_refresh($heap);

	$kernel->delay("star_cycle", $self->{star_delay});
}

# Grow a new tree.  Returns a list of lights to be cycled by timers
# later.

sub grow_tree {
	my $heap = shift;

	# Make sure Curses knows the current terminal size.

	my ($lines, $cols) = ($LINES, $COLS);
	eval {
		my $winsize = " " x 64;
		ioctl(STDOUT, &IO::Tty::Constant::TIOCGWINSZ, $winsize) or die $!;
		($lines, $cols) = unpack("S2", $winsize);
	};

	# TODO - How to do this portably?
	eval { resizeterm($lines, $cols) };

	# Clear the screen in the default color.  Add vertical bars to
	# either side of the screen, as this sometimes ensures erasure.

	attrset($heap->{color_bg});
	clear();
	addstr($_-1, 0, "|" . (" " x ($cols-2)) . "|") for 1..$lines;

	# Draw the tree.

	my $tier_width = 2;
	my $tier_height = 4;
	my $tier_width_increment = 8;
	my $light_density = 0.05;

	my $center = int($cols / 2);

	my $tier_pos = 4;

	my @tiers;

	TIER: while ($tier_pos < $lines - $tier_height) {
		for my $subtier (0..$tier_height-1) {
			last TIER if $tier_width >= $cols - 5;

			my $y = $tier_pos + $subtier;
			my $x = $center - int($tier_width / 2);
			my $w = $tier_width - 1;

			push @tiers, { y => $y, x => $x + 1, w => $w } if $w > 0;

			attrset($heap->{color_tree});
			addstr($y, $center - int($tier_width / 2), "/");
			addstr($y, $center + int($tier_width / 2), "\\");

			$tier_width += 2 * ($tier_width_increment / $tier_height);
		}

		$tier_pos += $tier_height;
		$tier_width -= $tier_width_increment;
	}

	# Distribute lights throughout the tree's area.



( run in 1.747 second using v1.01-cache-2.11-cpan-39bf76dae61 )