Commit | Line | Data |
7adfd53f |
1 | package Reaction::UI::View; |
2 | |
3 | use Reaction::Class; |
4 | |
5 | # declaring dependencies |
7adfd53f |
6 | use Reaction::UI::LayoutSet; |
7 | use Reaction::UI::RenderingContext; |
8a293e2e |
8 | use aliased 'Reaction::UI::Skin'; |
9 | use aliased 'Path::Class::Dir'; |
7adfd53f |
10 | |
11 | class View which { |
12 | |
de48f4e6 |
13 | has '_widget_class_cache' => (is => 'ro', default => sub { {} }); |
097e8442 |
14 | has '_widget_cache' => (is => 'ro', default => sub { {} }); |
7adfd53f |
15 | |
8a293e2e |
16 | has '_layout_set_cache' => (is => 'ro', default => sub { {} }); |
17 | |
7adfd53f |
18 | has 'app' => (is => 'ro', required => 1); |
19 | |
20 | has 'skin_name' => (is => 'ro', required => 1); |
21 | |
8a293e2e |
22 | has 'skin' => ( |
23 | is => 'ro', lazy_build => 1, |
24 | handles => [ qw(create_layout_set search_path_for_type) ] |
25 | ); |
26 | |
7adfd53f |
27 | has 'layout_set_class' => (is => 'ro', lazy_build => 1); |
28 | |
29 | has 'rendering_context_class' => (is => 'ro', lazy_build => 1); |
30 | |
89939ff9 |
31 | implements '_build_layout_set_class' => as { |
32 | my ($self) = @_; |
33 | return $self->find_related_class('LayoutSet'); |
34 | }; |
35 | |
36 | implements '_build_rendering_context_class' => as { |
37 | my ($self) = @_; |
38 | return $self->find_related_class('RenderingContext'); |
39 | }; |
40 | |
8a293e2e |
41 | implements '_build_skin' => as { |
42 | my ($self) = @_; |
43 | Skin->new( |
44 | name => $self->skin_name, view => $self, |
45 | skin_base_path => # returns a File, not a Dir. Thanks, Catalyst. |
46 | Dir->new($self->app->path_to('share', 'skin', $self->skin_name)), |
47 | ); |
48 | }; |
49 | |
7adfd53f |
50 | implements 'COMPONENT' => as { |
51 | my ($class, $app, $args) = @_; |
52 | return $class->new(%{$args||{}}, app => $app); |
53 | }; |
54 | |
55 | implements 'render_window' => as { |
56 | my ($self, $window) = @_; |
57 | my $root_vp = $window->focus_stack->vp_head; |
d193faa5 |
58 | my $rctx = $self->create_rendering_context; |
f2fef590 |
59 | my ($widget, $args) = $self->render_viewport_args($root_vp); |
60 | $widget->render(widget => $rctx, $args); |
7adfd53f |
61 | }; |
62 | |
f2fef590 |
63 | implements 'render_viewport_args' => as { |
64 | my ($self, $vp) = @_; |
7adfd53f |
65 | my $layout_set = $self->layout_set_for($vp); |
7adfd53f |
66 | my $widget = $self->widget_for($vp, $layout_set); |
f2fef590 |
67 | return ($widget, { viewport => $vp }); |
7adfd53f |
68 | }; |
69 | |
70 | implements 'widget_for' => as { |
71 | my ($self, $vp, $layout_set) = @_; |
097e8442 |
72 | return |
73 | $self->_widget_cache->{$layout_set->name} |
f2fef590 |
74 | ||= $layout_set->widget_class |
75 | ->new( |
76 | view => $self, layout_set => $layout_set |
77 | ); |
7adfd53f |
78 | }; |
79 | |
80 | implements 'widget_class_for' => as { |
81 | my ($self, $layout_set) = @_; |
5a1a893e |
82 | my $base = $self->blessed; |
8a293e2e |
83 | my $widget_type = $layout_set->widget_type; |
e22de101 |
84 | my $app_name = ref $self->app || $self->app; |
8a293e2e |
85 | return $self->_widget_class_cache->{$widget_type} ||= do { |
86 | |
87 | my @search_path = ($base, $app_name, 'Reaction::UI'); |
88 | my @haystack = map { join('::', $_, 'Widget', $widget_type) } |
89 | @search_path; |
90 | my $found; |
91 | foreach my $class (@haystack) { |
92 | #here we should throw if exits and error instead of eating the error |
93 | #only next when !exists |
94 | eval { Class::MOP::load_class($class) }; |
95 | #$@ ? next : return $class; |
96 | #warn "Loaded ${class}" unless $@; |
97 | #warn "Boom loading ${class}: $@" if $@; |
98 | unless ($@) { |
99 | $found = $class; |
100 | last; |
101 | } |
102 | } |
103 | unless ($found) { |
104 | confess "Couldn't load widget '${widget_type}'" |
105 | ." for layout '${\$layout_set->name}':" |
106 | ." tried: ".join(", ", @haystack); |
107 | } |
108 | $found; |
109 | }; |
7adfd53f |
110 | }; |
111 | |
112 | implements 'layout_set_for' => as { |
113 | my ($self, $vp) = @_; |
e22de101 |
114 | #print STDERR "Getting layoutset for VP ".(ref($vp) || "SC:".$vp)."\n"; |
7adfd53f |
115 | my $lset_name = eval { $vp->layout }; |
116 | confess "Couldn't call layout method on \$vp arg ${vp}: $@" if $@; |
117 | unless (length($lset_name)) { |
6ab43711 |
118 | my $vp_class = ref($vp) || $vp; |
119 | my ($last) = ($vp_class =~ /.*(?:::ViewPort::)(.+?)$/); |
120 | my @fragments = split('::', $last); |
121 | $_ = join("_", split(/(?=[A-Z])/, $_)) for @fragments; |
122 | $lset_name = lc(join('/', @fragments)); |
e22de101 |
123 | #print STDERR "--- $vp_class is rendered as $lset_name\n"; |
7adfd53f |
124 | } |
125 | my $cache = $self->_layout_set_cache; |
126 | return $cache->{$lset_name} ||= $self->create_layout_set($lset_name); |
127 | }; |
128 | |
7adfd53f |
129 | implements 'find_related_class' => as { |
130 | my ($self, $rel) = @_; |
6ab43711 |
131 | my $own_class = ref($self) || $self; |
7adfd53f |
132 | confess View." is abstract, you must subclass it" if $own_class eq View; |
133 | foreach my $super ($own_class->meta->class_precedence_list) { |
134 | next if $super eq View; |
135 | if ($super =~ /::View::/) { |
136 | (my $class = $super) =~ s/::View::/::${rel}::/; |
137 | if (eval { Class::MOP::load_class($class) }) { |
138 | return $class; |
139 | } |
140 | } |
141 | } |
142 | confess "Unable to find related ${rel} class for ${own_class}"; |
143 | }; |
144 | |
7adfd53f |
145 | implements 'create_rendering_context' => as { |
146 | my ($self, @args) = @_; |
147 | return $self->rendering_context_class->new( |
148 | $self->rendering_context_args_for(@args), |
149 | @args, |
150 | ); |
151 | }; |
152 | |
7adfd53f |
153 | implements 'rendering_context_args_for' => as { |
154 | return (); |
155 | }; |
156 | |
8a293e2e |
157 | implements 'layout_set_args_for' => as { |
158 | return (); |
159 | }; |
160 | |
7adfd53f |
161 | }; |
162 | |
163 | 1; |