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