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