1 package Reaction::UI::View;
5 # declaring dependencies
6 use Reaction::UI::LayoutSet;
7 use Reaction::UI::RenderingContext;
8 use aliased 'Reaction::UI::Skin';
9 use aliased 'Path::Class::Dir';
11 use namespace::clean -except => [ qw(meta) ];
14 has '_widget_cache' => (is => 'ro', default => sub { {} });
16 has '_layout_set_cache' => (is => 'ro', default => sub { {} });
18 has 'app' => (is => 'ro', required => 1);
20 has 'skin_name' => (is => 'ro', required => 1, default => 'default');
23 is => 'ro', lazy_build => 1,
24 handles => [ qw(create_layout_set search_path_for_type) ]
27 has 'layout_set_class' => (is => 'ro', lazy_build => 1);
29 has 'rendering_context_class' => (is => 'ro', lazy_build => 1);
30 sub _build_layout_set_class {
32 return $self->find_related_class('LayoutSet');
34 sub _build_rendering_context_class {
36 return $self->find_related_class('RenderingContext');
41 name => $self->skin_name, view => $self,
42 # path_to returns a File, not a Dir. Thanks, Catalyst.
43 skin_base_dir => Dir->new($self->app->path_to('share', 'skin')),
47 my ($class, $app, $args) = @_;
48 return $class->new(%{$args||{}}, app => $app);
51 my ($self, $window) = @_;
52 my $root_vp = $window->focus_stack->vp_head;
53 my $rctx = $self->create_rendering_context;
54 my ($widget, $args) = $self->render_viewport_args($root_vp);
55 $widget->render(widget => $rctx, $args);
57 sub render_viewport_args {
59 my $layout_set = $self->layout_set_for($vp);
60 my $widget = $self->widget_for($vp, $layout_set);
61 return ($widget, { viewport => $vp });
64 my ($self, $vp, $layout_set) = @_;
66 $self->_widget_cache->{$layout_set->name}
67 ||= $layout_set->widget_class
69 view => $self, layout_set => $layout_set
74 my $lset_name = eval { $vp->layout };
75 confess "Couldn't call layout method on \$vp arg ${vp}: $@" if $@;
76 $lset_name = $self->layout_set_name_from_viewport( blessed($vp) )
77 unless (length($lset_name));
78 my $cache = $self->_layout_set_cache;
79 return $cache->{$lset_name} ||= $self->create_layout_set($lset_name);
82 #XXX if it ever comes to it: this could be memoized. not bothering yet.
83 sub layout_set_name_from_viewport {
84 my ($self, $class) = @_;
85 my ($last) = ($class =~ /.*(?:::ViewPort::)(.+?)$/);
86 #split when a non-uppercase letter meets an uppercase or when an
87 #uppercase letter is followed by another uppercase and then a non-uppercase
88 #FooBar = foo_bar; Foo_Bar = foo_bar; FOOBar = foo_bar; FooBAR = foo_bar
90 join("_", split(/(?:(?<=[A-Z])(?=[A-Z][^_A-Z])|(?<=[^_A-Z])(?=[A-Z]))/, $_))
92 return lc(join('/', @fragments));
94 sub layout_set_file_extension {
95 confess View." is abstract, you must subclass it";
97 sub find_related_class {
98 my ($self, $rel) = @_;
99 my $own_class = ref($self) || $self;
100 confess View." is abstract, you must subclass it" if $own_class eq View;
101 foreach my $super ($own_class->meta->class_precedence_list) {
102 next if $super eq View;
103 if ($super =~ /::View::/) {
104 (my $class = $super) =~ s/::View::/::${rel}::/;
105 if (eval { Class::MOP::load_class($class) }) {
110 confess "Unable to find related ${rel} class for ${own_class}";
112 sub create_rendering_context {
113 my ($self, @args) = @_;
114 return $self->rendering_context_class->new(
115 $self->rendering_context_args_for(@args),
119 sub rendering_context_args_for {
122 sub layout_set_args_for {
126 __PACKAGE__->meta->make_immutable;