e582d9f0809bad052d6e67af11ce8e9c3ac2ba07
[catagits/Reaction.git] / lib / Reaction / UI / FocusStack.pm
1 package Reaction::UI::FocusStack;
2
3 use Reaction::Class;
4
5 use namespace::clean -except => [ qw(meta) ];
6
7
8 has vp_head => (
9   isa => 'Reaction::UI::ViewPort', is => 'rw',
10   clearer => 'clear_vp_head',
11 );
12 has vp_tail => (
13   isa => 'Reaction::UI::ViewPort', is => 'rw',
14   clearer => 'clear_vp_tail',
15 );
16 has vp_count => (
17   isa => 'Int', is => 'rw', required => 1, default => sub { 0 }
18 );
19 has loc_prefix => (isa => 'Str', is => 'rw', predicate => 'has_loc_prefix');
20 sub push_viewport {
21   my ($self, $class, %create) = @_;
22   my $tail = $self->vp_tail;
23   my $loc = $self->vp_count;
24   if ($self->has_loc_prefix) {
25     $loc = join('.', $self->loc_prefix, $loc);
26   }
27   my $vp = $class->new(
28              %create,
29              location => $loc,
30              focus_stack => $self,
31              (defined $tail ? ( outer => $tail ) : ()), # XXX possibly a bug in
32                                                         #immutable?
33            );
34   if ($tail) {           # if we already have a tail (non-empty vp stack)
35     $tail->inner($vp);     # set the current tail's inner vp to the new vp
36   } else {               # else we're currently an empty stack
37     $self->vp_head($vp);   # so set the head to the new vp
38   }
39   $self->vp_count($self->vp_count + 1);
40   $self->vp_tail($vp);
41   return $vp;
42 };
43 sub pop_viewport {
44   my ($self) = @_;
45   my $head = $self->vp_head;
46   confess "Can't pop from empty focus stack" unless defined($head);
47   my $vp = $self->vp_tail;
48   if ($vp eq $head) {
49     $self->clear_vp_head;
50     $self->clear_vp_tail;
51   } else {
52     $self->vp_tail($vp->outer);
53   }
54   $self->vp_count($self->vp_count - 1);
55   return $vp;
56 };
57 sub pop_viewports_to {
58   my ($self, $vp) = @_;
59   1 while ($self->pop_viewport ne $vp);
60   return $vp;
61 };
62 sub apply_events {
63   my $self = shift;
64   my $vp = $self->vp_tail;
65   while (defined $vp) {
66     $vp->apply_events(@_);
67     $vp = $vp->outer;
68   }
69 };
70   
71
72 __PACKAGE__->meta->make_immutable;
73
74
75 1;
76
77 =head1 NAME
78
79 Reaction::UI::FocusStack - A linked list of ViewPort-based objects
80
81 =head1 SYNOPSIS
82
83   my $stack = Reaction::UI::FocusStack->new();
84
85   # Or more commonly, in a Reaction::UI::RootController based
86   # Catalyst Controller:
87   my $stack = $ctx->focus_stack;
88
89   # Add a new basic viewport inside the last viewport on the stack:
90   my $vp = $stack->push_viewport('Reaction::UI::ViewPort' => 
91                                   layout => 'xhtml'
92                                 );
93
94   # Fetch the innermost viewport from the stack:
95   my $vp = $stack->pop_viewport();
96
97   # Remove all viewports inside a given viewport:
98   $stack->pop_viewports_to($vp);
99
100   # Create a named stack as a tangent to an existing viewport:
101   my $newstack = $vp->create_tangent('somename');
102
103   # Resolve current events using your stack:
104   # This is called by Reaction::UI::RootController in the end action.
105   $stack->apply_events($ctx, $param_hash);
106
107 =head1 DESCRIPTION
108
109 A FocusStack represents a list of related L<ViewPort|Reaction::UI::ViewPort>
110 objects. The L<Reaction::UI::RootController> creates an empty stack for you in
111 it's begin action, which represents the main thread/container of the page.
112 Typically you add new ViewPorts to this stack as the main parts of your page.
113 To add multiple parallel page subparts, create a tangent from the outer
114 viewport, and add more viewports as normal.
115
116 =head1 METHODS
117
118 =head2 new
119
120 =over
121
122 =item Arguments: none
123
124 =back
125
126 Create a new empty FocusStack. This is done for you in
127 L<Reaction::UI::RootController>.
128
129 =head2 push_viewport
130
131 =over
132
133 =item Arguments: $class, %options
134
135 =back
136
137 Creates a new L<Reaction::UI::ViewPort> based object and adds it to the stack.
138
139 The following attributes of the new ViewPort are set:
140
141 =over 
142
143 =item outer
144
145 Is set to the preceding ViewPort in the stack.
146
147 =item focus_stack
148
149 Is set to the FocusStack object that created the ViewPort.
150
151 =item location
152
153 Is set to the location of the ViewPort in the stack.
154
155 =back
156
157 =head2 pop_viewport
158
159 =over 
160
161 =item Arguments: none
162
163 =back
164
165 Removes the last/innermost ViewPort from the stack and returns it.
166
167 =head2 pop_viewports_to
168
169 =over 
170
171 =item Arguments: $viewport
172
173 =back
174
175 Pops all ViewPorts off the stack until the given ViewPort object
176 remains as the last item. If passed a $viewport not on the stack, this
177 will empty the stack completely (and then die complainingly).
178
179 TODO: Should pop_viewports_to check $vp->focus_stack eq $self first?
180
181 =head2 vp_head
182
183 =over
184
185 =item Arguments: none
186
187 =back
188
189 Retrieve the first ViewPort in this stack. Useful for calling
190 L<Reaction::UI::Window/render_viewport> on a
191 L<Reaction::UI::ViewPort/focus_tangent>.
192
193 =head2 vp_head
194
195 =over 
196
197 =item Arguments: none
198
199 =back
200
201 Retrieve the first ViewPort in this stack. Useful for calling
202 L<Reaction::UI::Window/render_viewport> on a
203 L<Reaction::UI::ViewPort/focus_tangent>.
204
205 =head2 vp_tail
206
207 =over 
208
209 =item Arguments: none
210
211 =back
212
213 Retrieve the last ViewPort in this stack. Useful for calling
214 L<Reaction::UI::Window/render_viewport> on a
215 L<Reaction::UI::ViewPort/focus_tangent>.
216
217 =head2 vp_count
218
219 =over 
220
221 =item Arguments: none
222
223 =back
224
225 =head2 loc_prefix
226
227 =head2 apply_events
228
229 =over 
230
231 =item Arguments: $ctx, $params_hashref
232
233 =back
234
235 Instruct each of the ViewPorts in the stack to apply the given events
236 to each of it's tangent stacks, and then to itself. These are applied
237 starting with the last/innermost ViewPort first.
238
239 =head1 AUTHORS
240
241 See L<Reaction::Class> for authors.
242
243 =head1 LICENSE
244
245 See L<Reaction::Class> for the license.
246
247 =cut