1 package Reaction::UI::Skin;
5 # declaring dependencies
6 use Reaction::UI::LayoutSet;
7 use Reaction::UI::RenderingContext;
12 use aliased 'Path::Class::Dir';
14 use namespace::clean -except => [ qw(meta) ];
17 has '_layout_set_cache' => (is => 'ro', default => sub { {} });
18 has '_widget_class_cache' => (is => 'ro', default => sub { {} });
20 has 'name' => (is => 'ro', isa => 'Str', required => 1);
21 has 'skin_dir' => (is => 'rw', isa => Dir, lazy_fail => 1);
23 has 'widget_search_path' => (
24 is => 'rw', isa => 'ArrayRef', required => 1, default => sub { [] }
28 is => 'ro', required => 1, weak_ref => 1,
29 handles => [ qw(layout_set_class) ],
33 is => 'rw', isa => Skin, required => 0, predicate => 'has_super',
37 my ($self, $args) = @_;
38 $self->_find_skin_dir($args);
39 $self->_load_skin_config($args);
42 my ($self, $args) = @_;
43 my $skin_name = $self->name;
44 if ($skin_name =~ s!^/(.*?)/!!) {
46 $args->{skin_base_dir} = eval {
47 Dir->new(File::ShareDir::dist_dir($dist))
51 # No installed Reaction
53 my $dir = Dir->new(dirname($file));
55 while ($dir->parent) {
56 if (-d $dir->subdir('share') && -d $dir->subdir('share')->subdir('skin')) {
57 $skin_base = $dir->subdir('share')->subdir('skin');
62 confess "could not find skinbase by recursion. ended up at $dir, from $file"
64 $args->{skin_base_dir} = $skin_base;
67 my $base = $args->{skin_base_dir}->subdir($skin_name);
68 confess "No such skin base directory ${base}"
70 $self->skin_dir($base);
72 sub _load_skin_config {
73 my ($self, $args) = @_;
74 my $base = $self->skin_dir;
75 my $lst = sub { (ref $_[0] eq 'ARRAY') ? $_[0] : [$_[0]] };
77 $args->{skin_base_dir}->file('defaults.conf'), $base->file('skin.conf')
79 # we get [ { $file => $conf }, ... ]
80 my %cfg = (map { %{(values %{$_})[0]} }
81 @{Config::Any->load_files({
82 files => [ grep { -e $_ } @files ],
86 if (my $super_name = $cfg{extends}) {
87 my $super = $self->new(
90 skin_base_dir => $args->{skin_base_dir},
94 if (exists $cfg{widget_search_path}) {
95 $self->widget_search_path($lst->($cfg{widget_search_path}));
97 # For some reason this conditional doesn't work correctly without
98 # the "my @x". Answers on a postcard.
99 unless (my @x = $self->full_widget_search_path) {
100 confess "No widget_search_path in defaults.conf or skin.conf"
102 ? " and no search path provided from super skin "
107 sub create_layout_set {
108 my ($self, $name) = @_;
109 $self->_create_layout_set($name, [], $self);
111 sub _create_layout_set {
112 my ($self, $name, $tried, $top_skin) = @_;
113 if (my $path = $self->layout_path_for($name)) {
114 return $self->layout_set_class->new(
115 $self->layout_set_args_for($name),
116 source_file => $path,
117 top_skin => $top_skin,
120 $tried = [ @{$tried}, $self->our_path_for_type('layout') ];
121 if ($self->has_super) {
122 return $self->super->_create_layout_set($name, $tried, $top_skin);
124 confess "Couldn't find layout set file for ${name}, tried "
125 .join(', ', @$tried);
127 sub layout_set_args_for {
128 my ($self, $name) = @_;
132 ($self->has_super ? (next_skin => $self->super) : ()),
133 $self->view->layout_set_args_for($name),
136 sub layout_path_for {
137 my ($self, $layout) = @_;
138 my $file_name = join(
139 '.', $layout, $self->view->layout_set_file_extension
141 my $path = $self->our_path_for_type('layout')
143 return (-e $path ? $path : undef);
145 sub search_path_for_type {
146 my ($self, $type) = @_;
148 $self->our_path_for_type($type),
150 ? @{$self->super->search_path_for_type($type)}
155 sub our_path_for_type {
156 my ($self, $type) = @_;
157 return $self->skin_dir->subdir($type)
159 sub full_widget_search_path {
162 @{$self->widget_search_path},
163 ($self->has_super ? $self->super->full_widget_search_path : ())
166 sub widget_class_for {
167 my ($self, $layout_set) = @_;
168 my $base = blessed($self);
169 my $widget_type = $layout_set->widget_type;
170 return $self->_widget_class_cache->{$widget_type} ||= do {
172 my @search_path = $self->full_widget_search_path;
173 my @haystack = map {join('::', $_, $widget_type)} @search_path;
175 foreach my $class (@haystack) {
176 #if the class is already loaded skip the call to Installed etc.
177 return $class if Class::MOP::is_class_loaded($class);
178 next unless Class::Inspector->installed($class);
180 my $ok = eval { Class::MOP::load_class($class) };
181 confess("Failed to load widget '${class}': $@") if $@;
184 confess "Couldn't locate widget '${widget_type}' for layout "
185 ."'${\$layout_set->name}': tried: ".join(", ", @haystack);
189 __PACKAGE__->meta->make_immutable;