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})); |
95 | } else { |
96 | confess "No widget_search_path in defaults.conf or skin.conf" |
97 | ." and no search path provided from super skin" |
98 | unless $self->full_widget_search_path; |
99 | } |
100 | } |
101 | sub create_layout_set { |
102 | my ($self, $name) = @_; |
103 | $self->_create_layout_set($name, [], $self); |
104 | }; |
105 | sub _create_layout_set { |
106 | my ($self, $name, $tried, $top_skin) = @_; |
107 | if (my $path = $self->layout_path_for($name)) { |
108 | return $self->layout_set_class->new( |
109 | $self->layout_set_args_for($name), |
110 | source_file => $path, |
111 | top_skin => $top_skin, |
112 | ); |
113 | } |
114 | $tried = [ @{$tried}, $self->our_path_for_type('layout') ]; |
115 | if ($self->has_super) { |
116 | return $self->super->_create_layout_set($name, $tried, $top_skin); |
117 | } |
118 | confess "Couldn't find layout set file for ${name}, tried " |
119 | .join(', ', @$tried); |
120 | }; |
121 | sub layout_set_args_for { |
122 | my ($self, $name) = @_; |
123 | return ( |
124 | name => $name, |
125 | skin => $self, |
126 | ($self->has_super ? (next_skin => $self->super) : ()), |
127 | $self->view->layout_set_args_for($name), |
128 | ); |
129 | }; |
130 | sub layout_path_for { |
131 | my ($self, $layout) = @_; |
132 | my $file_name = join( |
133 | '.', $layout, $self->view->layout_set_file_extension |
134 | ); |
135 | my $path = $self->our_path_for_type('layout') |
136 | ->file($file_name); |
137 | return (-e $path ? $path : undef); |
138 | }; |
139 | sub search_path_for_type { |
140 | my ($self, $type) = @_; |
141 | return [ |
142 | $self->our_path_for_type($type), |
143 | ($self->has_super |
144 | ? @{$self->super->search_path_for_type($type)} |
145 | : () |
146 | ) |
147 | ]; |
148 | }; |
149 | sub our_path_for_type { |
150 | my ($self, $type) = @_; |
151 | return $self->skin_dir->subdir($type) |
152 | }; |
153 | sub full_widget_search_path { |
154 | my ($self) = @_; |
155 | return ( |
156 | @{$self->widget_search_path}, |
157 | ($self->has_super ? $self->super->full_widget_search_path : ()) |
158 | ); |
159 | }; |
160 | sub widget_class_for { |
161 | my ($self, $layout_set) = @_; |
162 | my $base = blessed($self); |
163 | my $widget_type = $layout_set->widget_type; |
164 | return $self->_widget_class_cache->{$widget_type} ||= do { |
165 | |
166 | my @search_path = $self->full_widget_search_path; |
167 | my @haystack = map {join('::', $_, $widget_type)} @search_path; |
168 | |
169 | foreach my $class (@haystack) { |
170 | #if the class is already loaded skip the call to Installed etc. |
171 | return $class if Class::MOP::is_class_loaded($class); |
172 | next unless Class::Inspector->installed($class); |
173 | |
174 | my $ok = eval { Class::MOP::load_class($class) }; |
175 | confess("Failed to load widget '${class}': $@") if $@; |
176 | return $class; |
8a293e2e |
177 | } |
81393881 |
178 | confess "Couldn't locate widget '${widget_type}' for layout " |
179 | ."'${\$layout_set->name}': tried: ".join(", ", @haystack); |
a2a5872b |
180 | }; |
81393881 |
181 | }; |
a2a5872b |
182 | |
81393881 |
183 | __PACKAGE__->meta->make_immutable; |
a2a5872b |
184 | |
8a293e2e |
185 | |
186 | 1; |