--- deliantra/server/ext/widget.ext 2007/06/25 05:43:53 1.1 +++ deliantra/server/ext/widget.ext 2007/12/27 19:41:26 1.21 @@ -1,87 +1,197 @@ #! perl # mandatory depends=login # sends the following ext message types -# ws_n id # widgetset new -# ws_d id # widgetset destroy -# ws_c ws id class args # widgetset create -# w_c id [rid] name args # widget method call -# w_s id name value # widget member set -# w_g id rid name # widget member get +# 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_r rid res # widget call return -# w_e id name args # widget_event +# w_e id @args # widget_call + +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 ("\fw" => sub { + $weakns->{widget}{$_[0]} + }); }, ); -cf::player->attach ( - on_login => sub { - my ($pl) = @_; +sub csc_update_stats { + my ($ns) = @_; - #DEMO CODE - return unless $pl->ob->name eq "schmorp"; + while (my ($k, $v) = each %{ $ns->{csc}{stat} }) { + $v->set_text ($ns->pl->ob->stats->$k); + } +} - my $ns = $pl->ns; +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 demo_start { + my ($ns) = @_; + + my $ws = $ns->{csc} = $ns->new_widgetset; + + my ($tl, $entry) = $ws->template (inline => $cg_template, + [ + toplevel => {}, + entry => { + text => "xyz", + on_changed => sub { + warn "changed<@_>\n";#d# + }, + }, + ], + ); - return unless $ns->{can_widgetx}; + $tl->show; - my $ws = $ns->new_widgetset; + $ns->{xxxw} = [$tl, $entry];#d# + +# $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; + }, + ); - $ns->async (sub { - Coro::Timer::sleep 20; - warn "undef\n";#d# - undef $ws;# - });#d# - - my $w = $ws->new (Toplevel => - x => "center", - y => "center", - title => "Server Query", - has_close_button => 1, - on_delete => sub { - warn "i was being d-e-l-e-t-e-d\n"; - }, - ); + $w->add (my $ntb = $ws->new (Notebook => expand => 1)); - $w->add ($ws->new (Entry => - on_changed => sub { - warn "i was changed<@_>\n"; - } - )); + $ntb->add_tab (Statistics => (my $stats = $ws->new (Table => expand => 1)), "Basic statistics of your new character"); - $ns->async (sub { - warn $w->get ("parent"); - }); + $stats->add_at (0, 0, (my $statstable = $ws->new ("Table"))); - $w->show; + 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; -cf::register_exticmd w_e => sub { - my ($ns, $pkt) = @_; + $ws->{tl} = $w; + $w->show; +} - if (my $w = $ns->{widget}{$pkt->{id}}) { - if (my $cb = $w->{ev}{$pkt->{name}}) { - $_->($w, @{ $pkt->{args} || [] }) - for @$cb; - } - } +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_r => sub { - my ($ns, $pkt) = @_; +cf::register_exticmd w_e => sub { + my ($ns, $id, @args) = @_; - if (my $cb = delete $ns->{widget_return}{$pkt->{rid}}) { - $cb->(@{$pkt->{res} || [] }); + if (my $cb = $ns->{widget_cb}{$id}) { + $cb->(@args); } () @@ -95,14 +205,23 @@ my $ws = bless { id => $id, ns => $self, - w => {}, + _w => {}, }, "ext::widget::set"; - $ws->msg (ws_n => id => $id); + $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; @@ -114,49 +233,126 @@ sub destroy { my ($self) = @_; - $self->msg (ws_d => id => $self->{id}); + $self->msg (ws_d => $self->{id}); delete $self->{ns}; $_->destroy for values %{ $self->{w} }; } sub msg { - my ($self, $type, %msg) = @_; + my ($self, @msg) = @_; if (my $ns = shift->{ns}) { - $msg{msgtype} = $type; - $ns->send_packet ("ext " . cf::to_json \%msg); + warn "msg " . $ns->{json_coder}->encode (\@msg) if $DEBUG;#d# + $ns->send_packet ("ext " . $ns->{json_coder}->encode (\@msg)); } } -sub new { - my ($self, $class, %args) = @_; +sub alloc { + my ($self) = @_; - my $id = ++$self->{ns}{id}; + my $id = $self->{ns}->alloc_wid; - my $proxy = $self->{w}{$id} = bless { + my $proxy = bless { id => $id, }, "ext::widget::proxy"; - Scalar::Util::weaken ($proxy->{ws} = $self); 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) { - push @{$proxy->{ev}{$ev}}, $args{$ev}; - $args{$ev} = 0; + $args{$ev} = $proxy->{"_$ev"} = $proxy->cb ($args{$ev}); } $self->msg (ws_c => - ws => $self->{w}{id}, - id => $id, - class => $class, - args => \%args, + $self->{id}, + $proxy->{id}, + $class, + \%args, ); $proxy } +sub template { + my ($self, $type, $template, $args, $done_cb) = @_; + + my %cfg; + my @res; + + while (@$args) { + my ($name, $args) = splice @$args, 0, 2, (); + + 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) { + $args->{$ev} = $proxy->{"_$ev"} = $proxy->cb ($args->{$ev}); + } + + $cfg{$name} = { + %$args, + id => $proxy->{id}, + }; + + push @res, $proxy; + } + + if ($done_cb) { + my $proxy = $self->alloc; + my $ocb = $done_cb; + $done_cb = $proxy->cb (sub { + undef $proxy; + undef $done_cb; + &$ocb + }); + } + + $self->msg (ws_ct => + $self->{id}, + $type => $template, + $done_cb, + \%cfg, + ); + + @res +} + +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; @@ -167,42 +363,77 @@ delete $self->{ns}{widget}{$self->{id}}; if (my $ws = $self->{ws}) { - delete $ws->{w}{$self->{id}}; - $self->msg (w_c => name => "destroy"); + $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"; + + Scalar::Util::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 = $self->cb (sub { + undef $cb; + &$ocb + }); } + + $cb } sub msg { - my ($self, $type, %msg) = @_; + my ($self, $type, @arg) = @_; - if (my $ws = $self->{ws}) { - $ws->msg ($type, - %msg, - id => $self->{id}, - ); + 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, %msg) = @_; + my ($self, $cb, $type, @arg) = @_; if (my $ws = $self->{ws}) { - - my $rid = ++$ws->{ns}{id}; - - $self->msg ($type, %msg, rid => $rid); + my $rid = $ws->{ns}->alloc_wid; if ($cb) { - $ws->{ns}{widget_return}{$rid} = $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_return}{$rid} = sub { + $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; @@ -213,19 +444,19 @@ } sub set { - my ($self, $member, $value) = @_; + my ($self, @kv) = @_; - $self->msg (w_s => name => $member, value => $value); + $self->msg (w_s => \@kv); } sub get { my ($self, $member, $cb) = @_; - $self->msg_cb ($cb, w_g => name => $member); + $self->msg_cb ($cb, w_g => ref $member ? @$member : $member); } sub TO_JSON { - { __widget_ref__ => $_[0]{id} } + { "\fw" => $_[0]{id} } } our $AUTOLOAD; @@ -236,8 +467,24 @@ my $self = shift; - $self->msg (w_c => name => $AUTOLOAD, args => \@_); + #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} } +} +