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