package HexedUI; use strict; use warnings; use include; use Exporter; our @ISA = ("Exporter"); our @EXPORT = ("&forge_template"); use POE ("Wheel::Curses"); use Curses; use Cursed::Frame; use Cursed::Template; # The HexedUI singleton maintains the POE Curses session and is the lowest-level around # terminal resizing, key input, and other events. sub cast { my ($class, $framename, $layout) = @_; my $self = bless { _Keymaps => [], _Frame => $framename, _Shutdown => delete $layout->{Shutdown} // sub {} }, $class; # Set up the main event loop magic. POE::Session->create( inline_states => { _start => sub { $_[HEAP]{curses} = POE::Wheel::Curses->new( InputEvent => "keystroke_handler" ); $_[HEAP]{UI} = $self; $_[KERNEL]->alias_set("hexedui"); $_[KERNEL]->sig(WINCH => "termresize"); }, _stop => sub { system("clear"); endwin; }, keystroke_handler => sub { my $key = $_[ARG0]; if ($key lt " ") { $key = "<" . uc(unctrl($key)) . ">"; } elsif ($key =~ m/^\d{2,}$/) { $key = "<" . uc(keyname($key)) . ">"; } my $ui = $_[HEAP]{UI}; if ($key eq "<^C>") { delete $_[HEAP]{curses}; $ui->{_Shutdown}->(); exit; } else { $ui->{_Keymaps}[-1][1]->($ui->{_Keymaps}[-1][0], $key); } }, termresize => sub { # Delegate to each of the frames my $ui = $_[HEAP]{UI}; $_->frameresize foreach $ui->frames; $ui->{ $ui->{Frame} }->redraw; } } ); # Set up curses stuff. curs_set(0); # Yo mama uses a monochrome terminal. my @colors = ( qw(BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE) ); # Magically initialize all possible combos of foregrounds and backgrounds. my $cnt = 0; no strict "refs"; # Naughty! foreach my $bg (@colors) { foreach my $fg (@colors) { init_pair(++$cnt, &{ "COLOR_$fg" }, &{ "COLOR_$bg" }); } } use strict "refs"; # Create the frame. $self->{$framename} = Cursed::Frame->summon($self => $framename, %$layout); return $self; } # Any window that does input (one at a time) needs to register a keymap so POE can call it. sub install_keymap { my ($self, $win, $sub) = @_; # Nastyish closure magic so the handler can know who it is. push @{ $self->{_Keymaps} }, [$win, $sub]; } # Starts everything. The user can do this themselves, it doesn't matter, as long as POE # runs. sub start { $poe_kernel->run; exit; } # Do a blocking style "wait for input" without actually blocking. sub wait_input { my $self = shift; $poe_kernel->run_one_timeslice until defined $self->{_GotInput}; return delete $self->{_GotInput}; } # A simple keymap to return input to wait_input(). sub input_handler { my $self = shift; return sub { my ($win, $key) = @_; $win->ui->{_GotInput} = $key; }; } # Returns a list of every frame. sub frames { my $self = shift; return map { $self->{$_} } grep(!/^_/, keys %$self); } # Pass onto the current frame, it's their job. sub choose { my $self = shift; $self->{ $self->{_Frame} }->choose(@_); } # Pass onto the current frame, it's their job. sub askfor { my $self = shift; $self->{ $self->{_Frame} }->askfor(@_); } # Destroy all of a window's registered keymaps. sub remove_keymap { my ($self, $win) = @_; # Messy pattern. Modifying arrays in for loops is... owch. my @new; foreach (@{ $self->{_Keymaps} }) { push @new, $_ unless $_->[0]{Name} eq $win; } @{ $self->{_Keymaps} } = @new; } # Handy way to get a template object. sub forge_template { die "call forge_template as exported method" if ref $_[0] eq "HexedUI"; return Cursed::Template->forge(@_); } 42;