remove class blocks from widget code
[catagits/Reaction.git] / lib / Reaction / UI / View.pm
1 package Reaction::UI::View;
2
3 use Reaction::Class;
4
5 # declaring dependencies
6 use Reaction::UI::LayoutSet;
7 use Reaction::UI::RenderingContext;
8 use aliased 'Reaction::UI::Skin';
9 use aliased 'Path::Class::Dir';
10
11 use namespace::clean -except => [ qw(meta) ];
12
13
14 has '_widget_cache' => (is => 'ro', default => sub { {} });
15
16 has '_layout_set_cache' => (is => 'ro', default => sub { {} });
17
18 has 'app' => (is => 'ro', required => 1);
19
20 has 'skin_name' => (is => 'ro', required => 1, default => 'default');
21
22 has 'skin' => (
23   is => 'ro', lazy_build => 1,
24   handles => [ qw(create_layout_set search_path_for_type) ]
25 );
26
27 has 'layout_set_class' => (is => 'ro', lazy_build => 1);
28
29 has 'rendering_context_class' => (is => 'ro', lazy_build => 1);
30 sub _build_layout_set_class {
31   my ($self) = @_;
32   return $self->find_related_class('LayoutSet');
33 };
34 sub _build_rendering_context_class {
35   my ($self) = @_;
36   return $self->find_related_class('RenderingContext');
37 };
38 sub _build_skin {
39   my ($self) = @_;
40   Skin->new(
41     name => $self->skin_name, view => $self,
42     # path_to returns a File, not a Dir. Thanks, Catalyst.
43     skin_base_dir => Dir->new($self->app->path_to('share', 'skin')),
44   );
45 };
46 sub COMPONENT {
47   my ($class, $app, $args) = @_;
48   return $class->new(%{$args||{}}, app => $app);
49 };
50 sub render_window {
51   my ($self, $window) = @_;
52   my $root_vp = $window->focus_stack->vp_head;
53   my $rctx = $self->create_rendering_context;
54   my ($widget, $args) = $self->render_viewport_args($root_vp);
55   $widget->render(widget => $rctx, $args);
56 };
57 sub render_viewport_args {
58   my ($self, $vp) = @_;
59   my $layout_set = $self->layout_set_for($vp);
60   my $widget = $self->widget_for($vp, $layout_set);
61   return ($widget, { viewport => $vp });
62 };
63 sub widget_for {
64   my ($self, $vp, $layout_set) = @_;
65   return
66     $self->_widget_cache->{$layout_set->name}
67       ||= $layout_set->widget_class
68                      ->new(
69                          view => $self, layout_set => $layout_set
70                        );
71 };
72 sub layout_set_for {
73   my ($self, $vp) = @_;
74   my $lset_name = eval { $vp->layout };
75   confess "Couldn't call layout method on \$vp arg ${vp}: $@" if $@;
76   $lset_name = $self->layout_set_name_from_viewport( blessed($vp) )
77     unless (length($lset_name));
78   my $cache = $self->_layout_set_cache;
79   return $cache->{$lset_name} ||= $self->create_layout_set($lset_name);
80 };
81
82 #XXX if it ever comes to it: this could be memoized. not bothering yet.
83 sub layout_set_name_from_viewport {
84   my ($self, $class) = @_;
85   my ($last) = ($class =~ /.*(?:::ViewPort::)(.+?)$/);
86   #split when a non-uppercase letter meets an uppercase or when an
87   #uppercase letter is followed by another uppercase and then a non-uppercase
88   #FooBar = foo_bar; Foo_Bar = foo_bar; FOOBar = foo_bar; FooBAR = foo_bar
89   my @fragments = map {
90     join("_", split(/(?:(?<=[A-Z])(?=[A-Z][^_A-Z])|(?<=[^_A-Z])(?=[A-Z]))/, $_))
91   } split('::', $last);
92   return lc(join('/', @fragments));
93 };
94 sub layout_set_file_extension {
95   confess View." is abstract, you must subclass it";
96 };
97 sub find_related_class {
98   my ($self, $rel) = @_;
99   my $own_class = ref($self) || $self;
100   confess View." is abstract, you must subclass it" if $own_class eq View;
101   foreach my $super ($own_class->meta->class_precedence_list) {
102     next if $super eq View;
103     if ($super =~ /::View::/) {
104       (my $class = $super) =~ s/::View::/::${rel}::/;
105       if (eval { Class::MOP::load_class($class) }) {
106         return $class;
107       }
108     }
109   }
110   confess "Unable to find related ${rel} class for ${own_class}";
111 };
112 sub create_rendering_context {
113   my ($self, @args) = @_;
114   return $self->rendering_context_class->new(
115            $self->rendering_context_args_for(@args),
116            @args,
117          );
118 };
119 sub rendering_context_args_for {
120   return ();
121 };
122 sub layout_set_args_for {
123   return ();
124 };
125
126 __PACKAGE__->meta->make_immutable;
127
128
129 1;