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