Deliantra-Client

 view release on metacpan or  search on metacpan

DC/UI.pm  view on Meta::CPAN

   my @widgets = values %WIDGET;

   $_->reconfigure
      for @widgets;
}

# call when resolution changes etc.
sub rescale_widgets {
   my ($sx, $sy) = @_;

   for my $widget (values %WIDGET) {
      if ($widget->{is_toplevel} || $widget->{c_rescale}) {
         $widget->{x} += int $widget->{w} * 0.5 if $widget->{x} =~ /^[0-9.]+$/;
         $widget->{y} += int $widget->{h} * 0.5 if $widget->{y} =~ /^[0-9.]+$/;

         $widget->{x}       = int 0.5 + $widget->{x}        * $sx if $widget->{x} =~ /^[0-9.]+$/;
         $widget->{w}       = int 0.5 + $widget->{w}        * $sx if exists $widget->{w};
         $widget->{force_w} = int 0.5 + $widget->{force_w}  * $sx if exists $widget->{force_w};
         $widget->{y}       = int 0.5 + $widget->{y}        * $sy if $widget->{y} =~ /^[0-9.]+$/;
         $widget->{h}       = int 0.5 + $widget->{h}        * $sy if exists $widget->{h};
         $widget->{force_h} = int 0.5 + $widget->{force_h}  * $sy if exists $widget->{force_h};

         $widget->{x} -= int $widget->{w} * 0.5 if $widget->{x} =~ /^[0-9.]+$/;
         $widget->{y} -= int $widget->{h} * 0.5 if $widget->{y} =~ /^[0-9.]+$/;

      }
   }

   reconfigure_widgets;
}

#############################################################################

package DC::UI::Event;

sub xy {
   $_[1]->coord2local ($_[0]{x}, $_[0]{y})
}

#############################################################################

package DC::UI::Base;

use common::sense;

use DC::OpenGL;

sub new {
   my $class = shift;

   my $self = bless {
      x          => "center",
      y          => "center",
      z          => 0,
      w          => undef,
      h          => undef,
      can_events => 1,
      @_
   }, $class;

   DC::weaken ($DC::UI::WIDGET{$self+0} = $self);

   for (keys %$self) {
      if (/^on_(.*)$/) {
         $self->connect ($1 => delete $self->{$_});
      }
   }

   if (my $layout = $DC::UI::LAYOUT->{$self->{name}}) {
      $self->{x}       = $layout->{x} * $DC::UI::ROOT->{alloc_w} if exists $layout->{x};
      $self->{y}       = $layout->{y} * $DC::UI::ROOT->{alloc_h} if exists $layout->{y};
      $self->{force_w} = $layout->{w} * $DC::UI::ROOT->{alloc_w} if exists $layout->{w};
      $self->{force_h} = $layout->{h} * $DC::UI::ROOT->{alloc_h} if exists $layout->{h};

      $self->{x} -= $self->{force_w} * 0.5 if exists $layout->{x};
      $self->{y} -= $self->{force_h} * 0.5 if exists $layout->{y};

      $self->show if $layout->{show};
   }

   $self
}

sub destroy {
   my ($self) = @_;

   $self->hide;
   $self->emit ("destroy");
   %$self = ();
}

sub TO_JSON {
   { "\fw" => $_[0]{s_id} }
}

sub show {
   my ($self) = @_;

   return if $self->{parent};

   $DC::UI::ROOT->add ($self);
}

sub set_visible {
   my ($self) = @_;

   return if $self->{visible};

   $self->{parent} && $self->{parent}{root}#d#
      or return ::clienterror ("set_visible called without parent ($self->{parent}) or root\n" => 1);

   $self->{root}    = $self->{parent}{root};
   $self->{visible} = $self->{parent}{visible} + 1;

   $self->emit (visibility_change => 1);

   $self->realloc if !exists $self->{req_w};

   $_->set_visible for $self->visible_children;
}

DC/UI.pm  view on Meta::CPAN

   my ($self, $ev, $x, $y) = @_;

   $self->grab_focus;

   0
}

sub connect {
   my ($self, $signal, $cb) = @_;

   push @{ $self->{signal_cb}{$signal} }, $cb;

   defined wantarray and Guard::guard {
      @{ $self->{signal_cb}{$signal} } = grep $_ != $cb,
         @{ $self->{signal_cb}{$signal} };
   }
}

sub disconnect_all {
   my ($self, $signal) = @_;

   delete $self->{signal_cb}{$signal};
}

my %has_coords = (
   button_down  => 1,
   button_up    => 1,
   mouse_motion => 1,
   mouse_wheel  => 1,
);

sub emit {
   my ($self, $signal, @args) = @_;

   # I do not really like this solution, but I do not like duplication
   # and needlessly verbose code, either.
   my @append
      = $has_coords{$signal}
        ? $args[0]->xy ($self)
        : ();

   #warn +(caller(1))[3] . "emit $signal on $self (parent $self->{parent})\n";#d#

   for my $cb (
      @{$self->{signal_cb}{$signal} || []},		# before
      ($self->can ("invoke_$signal") || sub { 1 }),	# closure
   ) {
      return $cb->($self, @args, @append) || next;
   }

   # parent
   $self->{parent} && $self->{parent}->emit ($signal, @args)
}

#sub find_widget {
# in .xs

sub set_parent {
   my ($self, $parent) = @_;

   DC::weaken ($self->{parent} = $parent);
   $self->set_visible if $parent->{visible};
}

sub realloc {
   my ($self) = @_;

   if ($self->{visible}) {
      return if $self->{root}{realloc}{$self+0};

      $self->{root}{realloc}{$self+0} = $self;
      $self->{root}->update;
   } else {
      delete $self->{req_w};
      delete $self->{req_h};
   }
}

sub update {
   my ($self) = @_;

   $self->{parent}->update
      if $self->{parent};
}

sub reconfigure {
   my ($self) = @_;

   $self->realloc;
   $self->update;
}

# using global variables seems a bit hacky, but passing through all drawing
# functions seems pointless.
our ($draw_x, $draw_y, $draw_w, $draw_h); # screen rectangle being drawn

#sub draw {
#CFPlus.xs

sub _draw {
   my ($self) = @_;

   warn "no draw defined for $self\n";
}

sub DESTROY {
   my ($self) = @_;

   return if DC::in_destruct;

   local $@;
   eval { $self->destroy };
   warn "exception during widget destruction: $@" if $@ & $@ != /during global destruction/;

   delete $WIDGET{$self+0};
}

#############################################################################

package DC::UI::DrawBG;

DC/UI.pm  view on Meta::CPAN

}

sub invoke_button_down {
   my ($self, $ev, $x, $y) = @_;

   if ($x >= $self->{padding_x} && $x < $self->{w} - $self->{padding_x}
       && $y >= $self->{padding_y} && $y < $self->{h} - $self->{padding_y}) {
      $self->toggle;
   } else {
      return 0
   }

   1
}

sub _draw {
   my ($self) = @_;

   $self->SUPER::_draw;

   glTranslate $self->{padding_x}, $self->{padding_y}, 0;

   my ($w, $h) = @$self{qw(w h)};

   my $s = List::Util::min $w - $self->{padding_x} * 2, $h - $self->{padding_y} * 2;

   glColor @{ $FOCUS == $self ? $self->{active_fg} : $self->{fg} };

   my $tex = $self->{state} ? $tex[1] : $tex[0];

   glEnable GL_TEXTURE_2D;
   $tex->draw_quad_alpha (0, 0, $s, $s);
   glDisable GL_TEXTURE_2D;
}

#############################################################################

package DC::UI::Image;

our @ISA = DC::UI::DrawBG::;

use DC::OpenGL;

our %texture_cache;

sub new {
   my $class = shift;

   my $self = $class->SUPER::new (
      can_events => 0,
      scale      => 1,
      @_,
   );

   $self->{path} || $self->{tex}
      or Carp::croak "'path' or 'tex' attributes required";

   $self->{tex} ||= $texture_cache{$self->{path}} ||=
      new_from_resource DC::Texture $self->{path}, mipmap => 1;

   DC::weaken $texture_cache{$self->{path}};

   $self->{aspect} ||= $self->{tex}{w} / $self->{tex}{h};

   $self
}

sub STORABLE_freeze {
   my ($self, $cloning) = @_;

   $self->{path}
      or die "cannot serialise DC::UI::Image on non-loadable images\n";

   $self->{path}
}

sub STORABLE_attach {
   my ($self, $cloning, $path) = @_;

   $self->new (path => $path)
}

sub set_texture {
   my ($self, $tex) = @_;

   $self->{tex} = $tex;
   $self->update;
}

sub size_request {
   my ($self) = @_;

   (int $self->{tex}{w} * $self->{scale}, int $self->{tex}{h} * $self->{scale})
}

sub _draw {
   my ($self) = @_;

   $self->SUPER::_draw;

   my $tex = $self->{tex};

   my ($w, $h) = ($self->{w}, $self->{h});

   if ($self->{rot90}) {
      glRotate 90, 0, 0, 1;
      glTranslate 0, -$self->{w}, 0;

      ($w, $h) = ($h, $w);
   }

   glEnable GL_TEXTURE_2D;
   glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;

   $tex->draw_quad_alpha (0, 0, $w, $h);

   glDisable GL_TEXTURE_2D;
}

#############################################################################

DC/UI.pm  view on Meta::CPAN


#############################################################################

package DC::UI::Face;

our @ISA = DC::UI::DrawBG::;

use DC::OpenGL;

sub new {
   my $class = shift;

   my $self = $class->SUPER::new (
      size_w     => 32,
      size_h     => 8,
      aspect     => 1,
      can_events => 0,
      @_,
   );

   $self->update_anim;
   
   $self
}

sub update_timer {
   my ($self) = @_;

   return unless $self->{timer};

   if ($self->{visible}) {
      $self->{timer}->start;
   } else {
      $self->{timer}->stop;
   }
}

sub update_face {
   my ($self) = @_;

   if ($::CONN) {
      if (my $anim = $::CONN->{anim}[$self->{anim}]) {
         if ($anim && @$anim) {
            $self->{face} = $anim->[ $self->{frame} % @$anim ];
            delete $self->{face_change_cb};

            if (my $tex = $self->{tex} = $::CONN->{texture}[ $::CONN->{face}[$self->{face}]{id} ]) {
               unless ($tex->{name} || $tex->{loading}) {
                  $tex->upload (sub { $self->reconfigure });
               }
            }
         }
      }
   }
}

sub update_anim {
   my ($self) = @_;

   if ($self->{anim} && $self->{animspeed}) {
      DC::weaken (my $widget = $self);

      $self->{animspeed} = List::Util::max 0.05, $self->{animspeed};
      $self->{timer} = EV::periodic_ns 0, $self->{animspeed}, undef, sub {
         return unless $::CONN;

         my $w = $widget
            or return;

         ++$w->{frame};
         $w->update_face;

         # somehow, $widget can go away
         $w->update;
         $w->update_timer;
      };

      $self->update_face;
      $self->update_timer;
   } else {
      delete $self->{timer};
   }
}

sub size_request {
   my ($self) = @_;

   if ($::CONN) {
      if (my $faceid = $::CONN->{face}[$self->{face}]{id}) {
         if (my $tex = $self->{tex} = $::CONN->{texture}[$faceid]) {
            if ($tex->{name}) {
               return ($self->{size_w} || $tex->{w}, $self->{size_h} || $tex->{h});
            } elsif (!$tex->{loading}) {
               $tex->upload (sub { $self->reconfigure });
            }
         }

         $self->{face_change_cb} ||= $::CONN->on_face_change ($self->{face}, sub { $self->reconfigure });
      }
   }

   ($self->{size_w} || 8, $self->{size_h} || 8)
}

sub update {
   my ($self) = @_;

   return unless $self->{visible};

   $self->SUPER::update;
}

sub set_face {
   my ($self, $face) = @_;

   $self->{face} = $face;
   $self->reconfigure;
}

sub set_anim {
   my ($self, $anim) = @_;

DC/UI.pm  view on Meta::CPAN

      $self->{current}->set_visible if $self->{current} && $self->{visible};
      $self->{current}->configure (0, 0, $self->{w}, $self->{h});

      $self->emit (page_changed => $self->{current});
   }

   $self->realloc;
}

sub visible_children {
   $_[0]{current} || ()
}

sub size_request {
   my ($self) = @_;

   $self->{current}
      ? $self->{current}->size_request
      : (0, 0)
}

sub invoke_size_allocate {
   my ($self, $w, $h) = @_;

   $self->{current}->configure (0, 0, $w, $h)
     if $self->{current};

   1
}

sub _draw {
   my ($self) = @_;

   $self->{current}->draw
      if $self->{current};
}

#############################################################################

package DC::UI::Notebook;

use DC::OpenGL;

our @ISA = DC::UI::VBox::;

sub new {
   my $class = shift;

   my $self = $class->SUPER::new (
      buttonbar      => (new DC::UI::Buttonbar),
      multiplexer    => (new DC::UI::Multiplexer expand => 1),
      active_outline => [.7, .7, 0.2],
      # filter => # will be put between multiplexer and $self
      @_,
   );

   $self->{filter}->add ($self->{multiplexer}) if $self->{filter};
   $self->SUPER::add ($self->{buttonbar}, $self->{filter} || $self->{multiplexer});

   {
      Scalar::Util::weaken (my $wself = $self);

      $self->{multiplexer}->connect (c_add => sub {
         my ($mplex, $widgets) = @_;

         for my $child (@$widgets) {
            Scalar::Util::weaken $child;
            $child->{c_tab_} ||= do {
               my $tab =
                  (UNIVERSAL::isa $child->{c_tab}, "DC::UI::Base")
                     ? $child->{c_tab}
                     : new DC::UI::Button markup => $child->{c_tab}[0], tooltip => $child->{c_tab}[1];

               $tab->connect (activate => sub {
                  $wself->set_current_page ($child);
               });

               $tab
            };

            $self->{buttonbar}->add ($child->{c_tab_});
         }
      });

      $self->{multiplexer}->connect (c_remove => sub {
         my ($mplex, $widgets) = @_;

         for my $child (@$widgets) {
            $wself->{buttonbar}->remove ($child->{c_tab_});
         }
      });
   }

   $self
}

sub add {
   my ($self, @widgets) = @_;

   $self->{multiplexer}->add (@widgets)
}

sub remove {
   my ($self, @widgets) = @_;

   $self->{multiplexer}->remove (@widgets)
}

sub pages {
   my ($self) = @_;
   $self->{multiplexer}->children
}

sub page_index {
   my ($self, $widget) = @_;

   my $i = 0;
   for ($self->pages) {
      if ($_ eq $widget) { return $i };
      $i++;
   }

   undef
}

sub add_tab {
   my ($self, $title, $widget, $tooltip) = @_;

DC/UI.pm  view on Meta::CPAN


   $self
}

sub invoke_button_down {
   my ($self, $ev) = @_;

   my @menu_items;

   for (@{ $self->{options} }) {
      my ($value, $title, $tooltip) = @$_;

      push @menu_items, [$tooltip || $title, sub { $self->set_value ($value) }];
   }

   DC::UI::Menu->new (items => \@menu_items)->popup ($ev);
}

sub _set_value {
   my ($self, $value) = @_;

   my ($item) = grep $_->[0] eq $value, @{ $self->{options} };
   $item ||= $self->{options}[0]
      or return;

   $self->{value} = $item->[0];
   $self->set_markup ("$item->[1] ⇓");
#   $self->set_tooltip ($item->[2]);
}

sub set_value {
   my ($self, $value) = @_;

   return unless $self->{value} ne $value;

   $self->_set_value ($value);
   $self->emit (changed => $value);
}

sub set_options {
   my ($self, $options) = @_;

   $self->{options} = $options;
   $self->_set_value ($self->{value});
}

#############################################################################

package DC::UI::Statusbox;

our @ISA = DC::UI::VBox::;

sub new {
   my $class = shift;

   my $self = $class->SUPER::new (
      fontsize => 0.8,
      @_,
   );

   DC::weaken (my $this = $self);

   $self->{timer} = EV::timer 1, 1, sub { $this->reorder };

   $self
}

sub reorder {
   my ($self) = @_;
   my $NOW = EV::time;

   # freeze display when hovering over any label
   return if $DC::UI::TOOLTIP->{owner}
             && grep $DC::UI::TOOLTIP->{owner} == $_->{label},
                   values %{ $self->{item} };

   while (my ($k, $v) = each %{ $self->{item} }) {
      delete $self->{item}{$k} if $v->{timeout} < $NOW;
   }

   $self->{timer}->set (1, 1);

   my @widgets;

   my @items = sort {
                  $a->{pri} <=> $b->{pri}
                     or $b->{id} <=> $a->{id}
               } values %{ $self->{item} };

   my $count = 10 + 1;
   for my $item (@items) {
      last unless --$count;

      my $label = $item->{label} ||= do {
         # TODO: doesn't handle markup well (read as: at all)
         my $short = $item->{count} > 1
                     ? "<b>$item->{count} ×</b> $item->{text}"
                     : $item->{text};

         for ($short) {
            s/^\s+//;
            s/\s+/ /g;
         }

         new DC::UI::Label
            markup        => $short,
            tooltip       => $item->{tooltip},
            tooltip_font  => $::FONT_PROP,
            tooltip_width => 0.67,
            fontsize      => $item->{fontsize} || $self->{fontsize},
            max_w         => $::WIDTH * 0.44,
            align         => 0,
            fg            => [@{ $item->{fg} }],
            can_events    => 1,
            can_hover     => 1
      };

      if ((my $diff = $item->{timeout} - $NOW) < 2) {
         $label->{fg}[3] = ($item->{fg}[3] || 1) * $diff / 2;
         $label->update;
         $label->set_max_size (undef, $label->{req_h} * $diff)

DC/UI.pm  view on Meta::CPAN

         $item->{count} = 1;
         $item->{text} = $item->{tooltip} = $text;
      }
      $item->{id} += 0.2;#d#
      $item->{timeout} = $timeout;
      delete $item->{label};
   } else {
      $self->{item}{$group} = {
         id       => ++$self->{id},
         text     => $text,
         timeout  => $timeout,
         tooltip  => $text,
         fg       => [0.8, 0.8, 0.8, 0.8],
         pri      => 0,
         count    => 1,
         %arg,
      };
   }

   $ROOT->on_refresh (reorder => sub {
      $self->reorder;
   });
}

sub reconfigure {
   my ($self) = @_;

   delete $_->{label}
      for values %{ $self->{item} || {} };

   $self->reorder;
   $self->SUPER::reconfigure;
}

sub destroy {
   my ($self) = @_;

   $self->{timer}->cancel;

   $self->SUPER::destroy;
}

#############################################################################

package DC::UI::Root;

our @ISA = DC::UI::Container::;

use List::Util qw(min max);

use DC::OpenGL;

sub new {
   my $class = shift;

   my $self = $class->SUPER::new (
      visible => 1,
      @_,
   );

   DC::weaken ($self->{root} = $self);

   $self
}

sub size_request {
   my ($self) = @_;

   ($self->{w}, $self->{h})
}

sub _to_pixel {
   my ($coord, $size, $max) = @_;

   $coord =
      $coord eq "center" ? ($max - $size) * 0.5
    : $coord eq "max"    ? $max
    :                      $coord;

   $coord = 0            if $coord < 0;
   $coord = $max - $size if $coord > $max - $size;

   int $coord + 0.5
}

sub invoke_size_allocate {
   my ($self, $w, $h) = @_;

   for my $child ($self->children) {
      my ($X, $Y, $W, $H) = @$child{qw(x y req_w req_h)};

      $X = $child->{force_x} if exists $child->{force_x};
      $Y = $child->{force_y} if exists $child->{force_y};

      $X = _to_pixel $X, $W, $self->{w};
      $Y = _to_pixel $Y, $H, $self->{h};

      $child->configure ($X, $Y, $W, $H);
   }

   1
}

sub coord2local {
   my ($self, $x, $y) = @_;

   ($x, $y)
}

sub coord2global {
   my ($self, $x, $y) = @_;

   ($x, $y)
}

sub update {
   my ($self) = @_;

   $::WANT_REFRESH = 1;
}



( run in 2.100 seconds using v1.01-cache-2.11-cpan-437f7b0c052 )