r18493@agaton (orig r702): wreis | 2008-05-02 22:28:12 +0100
[catagits/Reaction.git] / lib / Reaction / UI / Skin.pm
CommitLineData
8a293e2e 1package Reaction::UI::Skin;
2
3use Reaction::Class;
4
5# declaring dependencies
6use Reaction::UI::LayoutSet;
7use Reaction::UI::RenderingContext;
8b498574 8use File::ShareDir;
888532d3 9use File::Basename;
8a293e2e 10
11use aliased 'Path::Class::Dir';
12
13class 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
1941;