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