Commit | Line | Data |
8a293e2e |
1 | package Reaction::UI::Skin; |
2 | |
3 | use Reaction::Class; |
4 | |
5 | # declaring dependencies |
6 | use Reaction::UI::LayoutSet; |
7 | use Reaction::UI::RenderingContext; |
8b498574 |
8 | use File::ShareDir; |
888532d3 |
9 | use File::Basename; |
8a293e2e |
10 | |
11 | use aliased 'Path::Class::Dir'; |
12 | |
13 | class Skin which { |
14 | |
15 | has '_layout_set_cache' => (is => 'ro', default => sub { {} }); |
a2a5872b |
16 | has '_widget_class_cache' => (is => 'ro', default => sub { {} }); |
8a293e2e |
17 | |
8b498574 |
18 | has 'name' => (is => 'ro', isa => 'Str', required => 1); |
19 | has 'skin_dir' => (is => 'rw', isa => Dir, lazy_fail => 1); |
8a293e2e |
20 | |
a2a5872b |
21 | has 'widget_search_path' => ( |
22 | is => 'rw', isa => 'ArrayRef', requred => 1, default => sub { [] } |
23 | ); |
349a57a4 |
24 | |
8a293e2e |
25 | has 'view' => ( |
26 | is => 'ro', required => 1, weak_ref => 1, |
a2a5872b |
27 | handles => [ qw(layout_set_class) ], |
8a293e2e |
28 | ); |
29 | |
30 | has 'super' => ( |
31 | is => 'rw', isa => Skin, required => 0, predicate => 'has_super', |
32 | ); |
33 | |
34 | sub BUILD { |
8b498574 |
35 | my ($self, $args) = @_; |
36 | $self->_find_skin_dir($args); |
37 | $self->_load_skin_config($args); |
8a293e2e |
38 | } |
39 | |
8b498574 |
40 | implements '_find_skin_dir' => as { |
41 | my ($self, $args) = @_; |
42 | my $skin_name = $self->name; |
43 | if ($skin_name =~ s!^/(.*?)/!!) { |
44 | my $dist = $1; |
888532d3 |
45 | $args->{skin_base_dir} = eval { |
46 | Dir->new(File::ShareDir::dist_dir($dist)) |
47 | ->subdir('skin'); |
48 | }; |
49 | if ($@) { |
50 | # No installed Reaction |
51 | my $file = __FILE__; |
52 | my $dir = Dir->new(dirname($file)); |
53 | my $skin_base; |
54 | while ($dir->parent) { |
55 | if (-d $dir->subdir('share') && -d $dir->subdir('share')->subdir('skin')) { |
56 | $skin_base = $dir->subdir('share')->subdir('skin'); |
57 | last; |
58 | } |
59 | $dir = $dir->parent; |
60 | } |
61 | confess "could not find skinbase by recursion. ended up at $dir, from $file" |
62 | unless $skin_base; |
63 | $args->{skin_base_dir} = $skin_base; |
64 | } |
8b498574 |
65 | } |
66 | my $base = $args->{skin_base_dir}->subdir($skin_name); |
8a293e2e |
67 | confess "No such skin base directory ${base}" |
68 | unless -d $base; |
8b498574 |
69 | $self->skin_dir($base); |
70 | }; |
71 | |
72 | implements '_load_skin_config' => as { |
73 | my ($self, $args) = @_; |
74 | my $base = $self->skin_dir; |
a2a5872b |
75 | my $lst = sub { (ref $_[0] eq 'ARRAY') ? $_[0] : [$_[0]] }; |
349a57a4 |
76 | my @files = ( |
8b498574 |
77 | $args->{skin_base_dir}->file('defaults.conf'), $base->file('skin.conf') |
349a57a4 |
78 | ); |
79 | # we get [ { $file => $conf }, ... ] |
80 | my %cfg = (map { %{(values %{$_})[0]} } |
81 | @{Config::Any->load_files({ |
82 | files => [ grep { -e $_ } @files ], |
83 | use_ext => 1, |
84 | })} |
85 | ); |
86 | if (my $super_name = $cfg{extends}) { |
349a57a4 |
87 | my $super = $self->new( |
8b498574 |
88 | name => $super_name, |
89 | view => $self->view, |
90 | skin_base_dir => $args->{skin_base_dir}, |
349a57a4 |
91 | ); |
92 | $self->super($super); |
93 | } |
94 | if (exists $cfg{widget_search_path}) { |
95 | $self->widget_search_path($lst->($cfg{widget_search_path})); |
96 | } else { |
a2a5872b |
97 | confess "No widget_search_path in defaults.conf or skin.conf" |
98 | ." and no search path provided from super skin" |
99 | unless $self->full_widget_search_path; |
7c2bcb55 |
100 | } |
8a293e2e |
101 | } |
102 | |
103 | implements 'create_layout_set' => as { |
b419e52e |
104 | my ($self, $name) = @_; |
105 | $self->_create_layout_set($name, [], $self); |
106 | }; |
107 | |
108 | implements '_create_layout_set' => as { |
109 | my ($self, $name, $tried, $top_skin) = @_; |
8a293e2e |
110 | if (my $path = $self->layout_path_for($name)) { |
111 | return $self->layout_set_class->new( |
112 | $self->layout_set_args_for($name), |
113 | source_file => $path, |
b419e52e |
114 | top_skin => $top_skin, |
8a293e2e |
115 | ); |
116 | } |
b419e52e |
117 | $tried = [ @{$tried}, $self->our_path_for_type('layout') ]; |
8a293e2e |
118 | if ($self->has_super) { |
b419e52e |
119 | return $self->super->_create_layout_set($name, $tried, $top_skin); |
8a293e2e |
120 | } |
c964124f |
121 | confess "Couldn't find layout set file for ${name}, tried " |
122 | .join(', ', @$tried); |
8a293e2e |
123 | }; |
124 | |
125 | implements 'layout_set_args_for' => as { |
126 | my ($self, $name) = @_; |
127 | return ( |
128 | name => $name, |
8a293e2e |
129 | skin => $self, |
130 | ($self->has_super ? (next_skin => $self->super) : ()), |
131 | $self->view->layout_set_args_for($name), |
132 | ); |
133 | }; |
134 | |
135 | implements 'layout_path_for' => as { |
136 | my ($self, $layout) = @_; |
137 | my $file_name = join( |
349a57a4 |
138 | '.', $layout, $self->view->layout_set_file_extension |
8a293e2e |
139 | ); |
140 | my $path = $self->our_path_for_type('layout') |
141 | ->file($file_name); |
142 | return (-e $path ? $path : undef); |
143 | }; |
144 | |
145 | implements 'search_path_for_type' => as { |
146 | my ($self, $type) = @_; |
147 | return [ |
148 | $self->our_path_for_type($type), |
149 | ($self->has_super |
150 | ? @{$self->super->search_path_for_type($type)} |
151 | : () |
152 | ) |
153 | ]; |
154 | }; |
155 | |
156 | implements 'our_path_for_type' => as { |
157 | my ($self, $type) = @_; |
8b498574 |
158 | return $self->skin_dir->subdir($type) |
8a293e2e |
159 | }; |
160 | |
a2a5872b |
161 | implements 'full_widget_search_path' => as { |
162 | my ($self) = @_; |
163 | return ( |
164 | @{$self->widget_search_path}, |
165 | ($self->has_super ? $self->super->full_widget_search_path : ()) |
166 | ); |
167 | }; |
168 | |
169 | implements 'widget_class_for' => as { |
170 | my ($self, $layout_set) = @_; |
ed5b203f |
171 | my $base = blessed($self); |
a2a5872b |
172 | my $widget_type = $layout_set->widget_type; |
173 | return $self->_widget_class_cache->{$widget_type} ||= do { |
174 | |
175 | my @search_path = $self->full_widget_search_path; |
176 | my @haystack = map {join('::', $_, $widget_type)} @search_path; |
177 | |
178 | foreach my $class (@haystack) { |
179 | #if the class is already loaded skip the call to Installed etc. |
180 | return $class if Class::MOP::is_class_loaded($class); |
181 | next unless Class::Inspector->installed($class); |
182 | |
183 | my $ok = eval { Class::MOP::load_class($class) }; |
184 | confess("Failed to load widget '${class}': $@") if $@; |
185 | return $class; |
186 | } |
187 | confess "Couldn't locate widget '${widget_type}' for layout " |
188 | ."'${\$layout_set->name}': tried: ".join(", ", @haystack); |
189 | }; |
190 | }; |
191 | |
8a293e2e |
192 | }; |
193 | |
194 | 1; |