#! 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 # ws_ct ws ttype ttext done_cb \%cfg # widgetset create from template # 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_e id @args # widget_call our $DEBUG = 1; cf::client->attach ( on_connect => sub { my ($ns) = @_; $ns->{id} = "a"; $ns->{json_coder}->filter_json_single_key_object ("\fw" => sub { $ns->{widget}{$_[0]} }); cf::weaken $ns; }, ); sub csc_update_stats { my ($ns) = @_; while (my ($k, $v) = each %{ $ns->{csc}{stat} }) { $v->set_text ($ns->pl->ob->stats->$k); } } my $cg_template = eval < { s_id => "toplevel", title => "Character Creation", x => "center", y => "center", z => 5, force_w => 760, force_h => 440, s_cl => [VBox => { s_cl => [ Label => { text => "Character Creation", fontsize => 1, align => 0, }, Label => { markup => "View or Edit your character attributes below, then press Finish to create your character", fontsize => 0.8, align => 0, }, HBox => { s_cl => [ Face => { s_id => "face", face => 0, bg => [.2, .2, .2, 1], min_w => 64, min_h => 64, }, Label => { s_id => "desc", fontsize => 0.8, ellipsize => 0, }, ]}, Notebook => { expand => 1, s_cl => [ Table => { c_tab => ["Basics", "Title, background and other information of your character."], }, Table => { c_tab => ["Stats", "Your character's primary stats such as strength, dexterity and so on."], }, Table => { c_tab => ["Race", "Your character's race."], }, Table => { c_tab => ["Class", "Your character's initial class."], }, ], }, Button => { s_id => "finish", text => "Finish", }, ]}], }, ] EOF die if $@; 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; # my ($tl, $entry) = $ws->template (inline => $cg_template, # [ # toplevel => {}, # entry => { # text => "xyz", # on_changed => sub { # warn "changed<@_>\n";#d# # }, # }, # ], # ); # # $tl->show; # # $ns->{xxxw} = [$tl, $entry];#d# # # $ws->find ("setup_notebook")->add ($ws->{tab}); # $ws->find ("setup_dialog")->toggle_visibility; } cf::player->attach ( on_login => sub { my ($pl) = @_; my $ns = $pl->ns; return unless $ns->{can_widget}; return unless $cf::CFG{devel}; #csc_start $ns; }, ); cf::register_exticmd w_e => sub { my ($ns, $id, @args) = @_; if (my $cb = $ns->{widget_cb}{$id}) { $cb->(@args); } () }; sub cf::client::new_widgetset { my ($self) = @_; my $id = ++$self->{id}; my $ws = bless { id => $id, ns => $self, _w => {}, }, "ext::widget::set"; cf::weaken $ws->{ns}; $ws->msg (ws_n => $id); $ws } sub cf::client::alloc_wid { pop @{ $_[0]{ids} } or ++$_[0]{id} } sub cf::client::free_wid { push @{ $_[0]{ids} }, $_[1]; } ############################################################################# 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}) { return unless $ns->{json_coder};#d# might be gone at destroy time(??) #d#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}->alloc_wid; my $proxy = bless { id => $id, }, "ext::widget::proxy"; cf::weaken ($proxy->{ns} = $self->{ns}); cf::weaken ($self->{ns}{widget}{$id} = $proxy); $proxy } sub new { my ($self, $class, %args) = @_; my $proxy = $self->alloc; cf::weaken +($self->{_w}{$proxy->{id}} = $proxy), +($proxy->{ws} = $self); for my $ev (grep /^on_/, keys %args) { $args{$ev} = $proxy->{"_$ev"} = $proxy->cb ($args{$ev}); } $self->msg (ws_c => $self->{id}, $proxy->{id}, $class, \%args, ); $proxy } sub template { my ($self, $type, $template, $args, $done_cb) = @_; my %cfg; while (@$args) { my ($name, $args) = splice @$args, 0, 2, (); my $proxy = $self->alloc; $self->{delete $args->{alias} or $name} = $proxy; cf::weaken +($self->{_w}{$proxy->{id}} = $proxy), +($proxy->{ws} = $self); for my $ev (grep /^on_/, keys %$args) { $args->{$ev} = $proxy->{"_$ev"} = $proxy->cb ($args->{$ev}); } $cfg{$name} = { %$args, id => $proxy->{id}, }; } if ($done_cb) { my $proxy = $self->alloc; my $ocb = $done_cb; $done_cb = $proxy->cb (sub { undef $proxy; undef $done_cb; &$ocb }); } if ($type eq "face") { $self->{ns}->send_face ($template, 100); $self->{ns}->flush_fx; } $self->msg (ws_ct => $self->{id}, $type => $template, $done_cb, \%cfg, ); } 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 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}->alloc_wid; if ($cb) { $ws->{ns}{widget_cb}{$rid} = sub { delete $ws->{ns}{widget_cb}{$rid}; $ws->{ns}->free_wid ($rid); &$cb }; $self->msg ($type, $rid, @arg); } else { # synchronous case my $wait = new Coro::Signal; my @res; $ws->{ns}{widget_cb}{$rid} = sub { delete $ws->{ns}{widget_cb}{$rid}; $ws->{ns}->free_wid ($rid); @res = @_; $wait->send; }; $self->msg ($type, $rid, @arg); $wait->wait; return @res; } } () } 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 cb { my ($self, $cb) = @_; my $proxy = bless { ns => $self->{ns}, id => $self->{ns}->alloc_wid, }, "ext::widget::callback"; cf::weaken $proxy->{ns}; $self->{ns}{widget_cb}{$proxy->{id}} = $cb; $proxy } sub oneshot_cb { my ($self, $cb) = @_; if ("CODE" eq ref $cb) { my $ocb = $cb; $cb = cb $self, sub { undef $cb; &$ocb }; } $cb } sub set { my ($self, @kv) = @_; $self->msg (w_s => \@kv); } sub get { my ($self, $member, $cb) = @_; $self->msg_cb ($cb, w_g => ref $member ? @$member : $member); } sub TO_JSON { { "\fw" => $_[0]{id} } } our $AUTOLOAD; sub AUTOLOAD { $AUTOLOAD =~ s/^.*:// or return; my $self = shift; #TODO: handle non-void context $self->msg (w_c => 0, $AUTOLOAD, @_); () } package ext::widget::callback; sub DESTROY { my ($self) = @_; if (my $ns = $self->{ns}) { delete $ns->{widget_cb}{$self->{id}}; $ns->free_wid ($self->{id}); } } sub TO_JSON { { "\fc" => $_[0]{id} } }