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