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 | |
81393881 |
13 | use namespace::clean -except => [ qw(meta) ]; |
14 | |
15 | |
16 | has '_layout_set_cache' => (is => 'ro', default => sub { {} }); |
17 | has '_widget_class_cache' => (is => 'ro', default => sub { {} }); |
18 | |
19 | has 'name' => (is => 'ro', isa => 'Str', required => 1); |
20 | has 'skin_dir' => (is => 'rw', isa => Dir, lazy_fail => 1); |
21 | |
22 | has 'widget_search_path' => ( |
23 | is => 'rw', isa => 'ArrayRef', requred => 1, default => sub { [] } |
24 | ); |
25 | |
26 | has 'view' => ( |
27 | is => 'ro', required => 1, weak_ref => 1, |
28 | handles => [ qw(layout_set_class) ], |
29 | ); |
30 | |
31 | has 'super' => ( |
32 | is => 'rw', isa => Skin, required => 0, predicate => 'has_super', |
33 | ); |
34 | |
35 | sub BUILD { |
36 | my ($self, $args) = @_; |
37 | $self->_find_skin_dir($args); |
38 | $self->_load_skin_config($args); |
39 | } |
40 | sub _find_skin_dir { |
41 | my ($self, $args) = @_; |
42 | my $skin_name = $self->name; |
43 | if ($skin_name =~ s!^/(.*?)/!!) { |
44 | my $dist = $1; |
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; |
8b498574 |
64 | } |
81393881 |
65 | } |
66 | my $base = $args->{skin_base_dir}->subdir($skin_name); |
67 | confess "No such skin base directory ${base}" |
68 | unless -d $base; |
69 | $self->skin_dir($base); |
70 | }; |
71 | sub _load_skin_config { |
72 | my ($self, $args) = @_; |
73 | my $base = $self->skin_dir; |
74 | my $lst = sub { (ref $_[0] eq 'ARRAY') ? $_[0] : [$_[0]] }; |
75 | my @files = ( |
76 | $args->{skin_base_dir}->file('defaults.conf'), $base->file('skin.conf') |
77 | ); |
78 | # we get [ { $file => $conf }, ... ] |
79 | my %cfg = (map { %{(values %{$_})[0]} } |
80 | @{Config::Any->load_files({ |
81 | files => [ grep { -e $_ } @files ], |
82 | use_ext => 1, |
83 | })} |
84 | ); |
85 | if (my $super_name = $cfg{extends}) { |
86 | my $super = $self->new( |
87 | name => $super_name, |
88 | view => $self->view, |
89 | skin_base_dir => $args->{skin_base_dir}, |
349a57a4 |
90 | ); |
81393881 |
91 | $self->super($super); |
8a293e2e |
92 | } |
81393881 |
93 | if (exists $cfg{widget_search_path}) { |
94 | $self->widget_search_path($lst->($cfg{widget_search_path})); |
b0c3f1b1 |
95 | } |
96 | # For some reason this conditional doesn't work correctly without |
97 | # the "my @x". Answers on a postcard. |
98 | unless (my @x = $self->full_widget_search_path) { |
81393881 |
99 | confess "No widget_search_path in defaults.conf or skin.conf" |
b0c3f1b1 |
100 | .($self->has_super |
101 | ? " and no search path provided from super skin " |
102 | .$self->super->name |
103 | : ""); |
81393881 |
104 | } |
105 | } |
106 | sub create_layout_set { |
107 | my ($self, $name) = @_; |
108 | $self->_create_layout_set($name, [], $self); |
109 | }; |
110 | sub _create_layout_set { |
111 | my ($self, $name, $tried, $top_skin) = @_; |
112 | if (my $path = $self->layout_path_for($name)) { |
113 | return $self->layout_set_class->new( |
114 | $self->layout_set_args_for($name), |
115 | source_file => $path, |
116 | top_skin => $top_skin, |
117 | ); |
118 | } |
119 | $tried = [ @{$tried}, $self->our_path_for_type('layout') ]; |
120 | if ($self->has_super) { |
121 | return $self->super->_create_layout_set($name, $tried, $top_skin); |
122 | } |
123 | confess "Couldn't find layout set file for ${name}, tried " |
124 | .join(', ', @$tried); |
125 | }; |
126 | sub layout_set_args_for { |
127 | my ($self, $name) = @_; |
128 | return ( |
129 | name => $name, |
130 | skin => $self, |
131 | ($self->has_super ? (next_skin => $self->super) : ()), |
132 | $self->view->layout_set_args_for($name), |
133 | ); |
134 | }; |
135 | sub layout_path_for { |
136 | my ($self, $layout) = @_; |
137 | my $file_name = join( |
138 | '.', $layout, $self->view->layout_set_file_extension |
139 | ); |
140 | my $path = $self->our_path_for_type('layout') |
141 | ->file($file_name); |
142 | return (-e $path ? $path : undef); |
143 | }; |
144 | sub search_path_for_type { |
145 | my ($self, $type) = @_; |
146 | return [ |
147 | $self->our_path_for_type($type), |
148 | ($self->has_super |
149 | ? @{$self->super->search_path_for_type($type)} |
150 | : () |
151 | ) |
152 | ]; |
153 | }; |
154 | sub our_path_for_type { |
155 | my ($self, $type) = @_; |
156 | return $self->skin_dir->subdir($type) |
157 | }; |
158 | sub full_widget_search_path { |
159 | my ($self) = @_; |
160 | return ( |
161 | @{$self->widget_search_path}, |
162 | ($self->has_super ? $self->super->full_widget_search_path : ()) |
163 | ); |
164 | }; |
165 | sub widget_class_for { |
166 | my ($self, $layout_set) = @_; |
167 | my $base = blessed($self); |
168 | my $widget_type = $layout_set->widget_type; |
169 | return $self->_widget_class_cache->{$widget_type} ||= do { |
170 | |
171 | my @search_path = $self->full_widget_search_path; |
172 | my @haystack = map {join('::', $_, $widget_type)} @search_path; |
173 | |
174 | foreach my $class (@haystack) { |
175 | #if the class is already loaded skip the call to Installed etc. |
176 | return $class if Class::MOP::is_class_loaded($class); |
177 | next unless Class::Inspector->installed($class); |
178 | |
179 | my $ok = eval { Class::MOP::load_class($class) }; |
180 | confess("Failed to load widget '${class}': $@") if $@; |
181 | return $class; |
8a293e2e |
182 | } |
81393881 |
183 | confess "Couldn't locate widget '${widget_type}' for layout " |
184 | ."'${\$layout_set->name}': tried: ".join(", ", @haystack); |
a2a5872b |
185 | }; |
81393881 |
186 | }; |
a2a5872b |
187 | |
81393881 |
188 | __PACKAGE__->meta->make_immutable; |
a2a5872b |
189 | |
8a293e2e |
190 | |
191 | 1; |