#! perl # mandatory depends=login # sends the following ext message types # ws_a id name... # associate well-known widget with given id # ws_n ws # widgetset new # ws_d ws # widgetset destroy # ws_c ws id class @args # widgetset create # w_c id rid name @args # widget method call # w_s id @attr # widget member set # w_g id rid @attr # widget member get # # and expects the following exti message types # w_r rid res # widget call return # w_e id rid @args # widget_event our $DEBUG = 1; cf::client->attach ( on_connect => sub { my ($ns) = @_; Scalar::Util::weaken (my $weakns = $ns); $ns->{id} = "a"; $ns->{json_coder}->filter_json_single_key_object (__w_ => sub { # cannot deserialise ATM undef }); }, ); sub csc_update_stats { my ($ns) = @_; while (my ($k, $v) = each %{ $ns->{csc}{stat} }) { $v->set_text ($ns->pl->ob->stats->$k); } } sub demo_start { my ($ns) = @_; my $ws = $ns->{csc} = $ns->new_widgetset; $ws->{tab} = $ws->new (Label => text => "dumb tst", c_tab => ["hull"]); $ws->find ("setup_notebook")->add ($ws->{tab}); $ws->find ("setup_dialog")->toggle_visibility; } sub csc_start { my ($ns) = @_; my $ws = $ns->{csc} = $ns->new_widgetset; my $w = $ws->new (Toplevel => min_w => 600, min_h => 400, x => "center", y => "center", title => "Character Creation", has_close_button => 1, on_delete => sub { $ws->destroy; }, ); $w->add (my $ntb = $ws->new (Notebook => expand => 1)); $ntb->add_tab (Statistics => (my $stats = $ws->new (Table => expand => 1)), "Basic statistics of your new character"); $stats->add_at (0, 0, (my $statstable = $ws->new ("Table"))); for ( [0, "Str"], [1, "Dex"], [2, "Con"], [3, "Int"], [4, "Wis"], [5, "Pow"], [6, "Cha"], ) { my ($x, $label) = @$_; $statstable->add_at ($x, 0, $ws->new (Label => can_hover => 1, can_events => 1, align => +1, text => $label, tooltip => "#stat_$label", )); $statstable->add_at ($x, 1, $ws->{stat}{$label} = $ws->new (Label => can_hover => 1, can_events => 1, align => +1, template => "88", tooltip => "#stat_$label", )); } csc_update_stats $ns; $ws->{tl} = $w; $w->show; } cf::player->attach ( on_login => sub { my ($pl) = @_; return unless $cf::CFG{devel}; my $ns = $pl->ns; return unless $ns->{can_widget}; #csc_start $ns; demo_start $ns; }, ); cf::register_exticmd w_e => sub { my ($ns, $id, $rid, @args) = @_; if (my $w = $ns->{widget}{$id}) { if (my $cb = $w->{ev}{$rid}) { $cb->($w, @args); } } () }; cf::register_exticmd w_r => sub { my ($ns, $rid, $res) = @_; if (my $cb = delete $ns->{widget_return}{$rid}) { $cb->(@$res); } () }; sub cf::client::new_widgetset { my ($self) = @_; my $id = ++$self->{id}; my $ws = bless { id => $id, ns => $self, _w => {}, }, "ext::widget::set"; $ws->msg (ws_n => $id); $ws } ############################################################################# package ext::widget::set; sub DESTROY { $_[0]->destroy; } sub destroy { my ($self) = @_; $self->msg (ws_d => $self->{id}); delete $self->{ns}; $_->destroy for values %{ $self->{w} }; } sub msg { my ($self, @msg) = @_; if (my $ns = shift->{ns}) { warn "msg " . $ns->{json_coder}->encode (\@msg) if $DEBUG;#d# $ns->send_packet ("ext " . $ns->{json_coder}->encode (\@msg)); } } sub alloc { my ($self) = @_; my $id = ++$self->{ns}{id}; my $proxy = bless { id => $id, }, "ext::widget::proxy"; Scalar::Util::weaken ($proxy->{ns} = $self->{ns}); Scalar::Util::weaken ($self->{ns}{widget}{$id} = $proxy); $proxy } sub new { my ($self, $class, %args) = @_; my $proxy = $self->alloc; Scalar::Util::weaken ($self->{_w}{$proxy->{id}} = $proxy); Scalar::Util::weaken ($proxy->{ws} = $self); for my $ev (grep /^on_/, keys %args) { my $rid = ++$self->{ns}{id}; $proxy->{ev}{$rid} = $args{$ev}; $args{$ev} = $rid; } $self->msg (ws_c => $self->{id}, $proxy->{id}, $class, \%args, ); $proxy } sub find { my ($self, @names) = @_; my @res; my @alloc; for my $name (@names) { push @res, $self->{ns}{widget_wkw}{$name} ||= do { my $proxy = $self->alloc; push @alloc, $proxy->{id} => $name; $proxy }; } $self->msg (ws_a => @alloc) if @alloc; wantarray ? @res : $res[0] } ############################################################################# package ext::widget::proxy; sub DESTROY { my ($self) = @_; delete $self->{ns}{widget}{$self->{id}}; if (my $ws = $self->{ws}) { $self->msg (w_c => 0, "destroy"); delete $ws->{_w}{$self->{id}}; } } sub msg { my ($self, $type, @arg) = @_; if (my $ns = $self->{ns}) { my @msg = ($type, $self->{id}, @arg); warn "MSG " . $ns->{json_coder}->encode (\@msg) if $DEBUG;#d# $ns->send_packet ("ext " . $ns->{json_coder}->encode (\@msg)); } } sub msg_cb { my ($self, $cb, $type, @arg) = @_; if (my $ws = $self->{ws}) { my $rid = ++$ws->{ns}{id}; $self->msg ($type, $rid, @arg); if ($cb) { $ws->{ns}{widget_return}{$rid} = $cb; } else { # synchronous case my $wait = new Coro::Signal; my @res; $ws->{ns}{widget_return}{$rid} = sub { @res = @_; $wait->send; }; $wait->wait; return @res; } } () } sub set { my ($self, @kv) = @_; $self->msg (w_s => \@kv); } sub get { my ($self, $member, $cb) = @_; $self->msg_cb ($cb, w_g => [$member]); } sub TO_JSON { { __w_ => $_[0]{id} } } our $AUTOLOAD; sub AUTOLOAD { $AUTOLOAD =~ s/^.*:// or return; my $self = shift; #TODO: handle non-void context $self->msg (w_c => 0, $AUTOLOAD, @_); () }