diff --git a/river/Cursor.zig b/river/Cursor.zig index ab78137..abca096 100644 --- a/river/Cursor.zig +++ b/river/Cursor.zig @@ -548,16 +548,20 @@ fn layerSurfaceAt( /// Find the topmost visible view surface (incl. popups) at ox,oy. fn viewSurfaceAt(output: Output, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*c.wlr_surface { // Focused views are rendered on top, so look for them first. - var it = ViewStack(View).iterator(output.views.first, output.current.tags); - while (it.next()) |node| { - if (node.view.current.focus == 0) continue; - if (node.view.surfaceAt(ox, oy, sx, sy)) |found| return found; + var it = ViewStack(View).iter(output.views.first, .forward, output.current.tags, surfaceAtFilter); + while (it.next()) |view| { + if (view.current.focus == 0) continue; + if (view.surfaceAt(ox, oy, sx, sy)) |found| return found; } - it = ViewStack(View).iterator(output.views.first, output.current.tags); - while (it.next()) |node| { - if (node.view.surfaceAt(ox, oy, sx, sy)) |found| return found; + it = ViewStack(View).iter(output.views.first, .forward, output.current.tags, surfaceAtFilter); + while (it.next()) |view| { + if (view.surfaceAt(ox, oy, sx, sy)) |found| return found; } return null; } + +fn surfaceAtFilter(view: *View, filter_tags: u32) bool { + return !view.destroying and view.current.tags & filter_tags != 0; +} diff --git a/river/Output.zig b/river/Output.zig index a05518b..794d833 100644 --- a/river/Output.zig +++ b/river/Output.zig @@ -97,7 +97,7 @@ pub fn init(self: *Self, root: *Root, wlr_output: *c.wlr_output) !void { layer.* = std.TailQueue(LayerSurface).init(); } - self.views.init(); + self.views = ViewStack(View){}; self.current = .{ .tags = 1 << 0, @@ -184,13 +184,10 @@ fn layoutFull(self: *Self, visible_count: u32) void { .height = self.usable_box.height - (2 * xy_offset), }; - var it = ViewStack(View).pendingIterator(self.views.first, self.pending.tags); - while (it.next()) |node| { - const view = &node.view; - if (!view.pending.float and !view.pending.fullscreen) { - view.pending.box = full_box; - view.applyConstraints(); - } + var it = ViewStack(View).iter(self.views.first, .forward, self.pending.tags, arrangeFilter); + while (it.next()) |view| { + view.pending.box = full_box; + view.applyConstraints(); } } @@ -285,32 +282,28 @@ fn layoutExternal(self: *Self, visible_count: u32) !void { // Apply window configuration to views var i: u32 = 0; - var view_it = ViewStack(View).pendingIterator(self.views.first, self.pending.tags); - while (view_it.next()) |node| { - const view = &node.view; - if (!view.pending.float and !view.pending.fullscreen and !view.destroying) { - view.pending.box = view_boxen.items[i]; - view.applyConstraints(); - i += 1; - } + var view_it = ViewStack(View).iter(self.views.first, .forward, self.pending.tags, arrangeFilter); + while (view_it.next()) |view| : (i += 1) { + view.pending.box = view_boxen.items[i]; + view.applyConstraints(); } } +fn arrangeFilter(view: *View, filter_tags: u32) bool { + return !view.destroying and !view.pending.float and + !view.pending.fullscreen and view.pending.tags & filter_tags != 0; +} + /// Arrange all views on the output for the current layout. Modifies only /// pending state, the changes are not appplied until a transaction is started /// and completed. pub fn arrangeViews(self: *Self) void { if (self == &self.root.noop_output) return; - const full_area = Box.fromWlrBox(c.wlr_output_layout_get_box(self.root.wlr_output_layout, self.wlr_output).*); - // Count up views that will be arranged by the layout var layout_count: u32 = 0; - var it = ViewStack(View).pendingIterator(self.views.first, self.pending.tags); - while (it.next()) |node| { - const view = &node.view; - if (!view.pending.float and !view.pending.fullscreen and !view.destroying) layout_count += 1; - } + var it = ViewStack(View).iter(self.views.first, .forward, self.pending.tags, arrangeFilter); + while (it.next() != null) layout_count += 1; // If the usable area has a zero dimension, trying to arrange the layout // would cause an underflow and is pointless anyway. diff --git a/river/OutputStatus.zig b/river/OutputStatus.zig index eac2bbd..af264d1 100644 --- a/river/OutputStatus.zig +++ b/river/OutputStatus.zig @@ -60,13 +60,14 @@ pub fn sendViewTags(self: Self) void { var view_tags = std.ArrayList(u32).init(util.gpa); defer view_tags.deinit(); - var it = ViewStack(View).iterator(self.output.views.first, std.math.maxInt(u32)); - while (it.next()) |node| + var it = self.output.views.first; + while (it) |node| : (it = node.next) { view_tags.append(node.view.current.tags) catch { - c.wl_resource_post_no_memory(self.wl_resource); - log.crit(.river_status, "out of memory", .{}); - return; - }; + c.wl_resource_post_no_memory(self.wl_resource); + log.crit(.river_status, "out of memory", .{}); + return; + }; + } var wl_array = c.wl_array{ .size = view_tags.items.len * @sizeOf(u32), diff --git a/river/Root.zig b/river/Root.zig index 6e7e99e..974bcee 100644 --- a/river/Root.zig +++ b/river/Root.zig @@ -125,12 +125,11 @@ pub fn startTransaction(self: *Self) void { // to reset the pending count to 0 and clear serials from the views self.pending_configures = 0; - // Iterate over all layout views of all outputs + // Iterate over all views of all outputs var output_it = self.outputs.first; - while (output_it) |node| : (output_it = node.next) { - const output = &node.data; - var view_it = ViewStack(View).iterator(output.views.first, std.math.maxInt(u32)); - while (view_it.next()) |view_node| { + while (output_it) |output_node| : (output_it = output_node.next) { + var view_it = output_node.data.views.first; + while (view_it) |view_node| : (view_it = view_node.next) { const view = &view_node.view; if (view.destroying) { diff --git a/river/Seat.zig b/river/Seat.zig index 96d320b..a1825ed 100644 --- a/river/Seat.zig +++ b/river/Seat.zig @@ -88,7 +88,7 @@ pub fn init(self: *Self, input_manager: *InputManager, name: [*:0]const u8) !voi self.focused = .none; - self.focus_stack.init(); + self.focus_stack = ViewStack(*View){}; self.status_trackers = std.SinglyLinkedList(SeatStatus).init(); @@ -111,41 +111,43 @@ pub fn deinit(self: *Self) void { /// Set the current focus. If a visible view is passed it will be focused. /// If null is passed, the first visible view in the focus stack will be focused. -pub fn focus(self: *Self, _view: ?*View) void { - var view = _view; +pub fn focus(self: *Self, _target: ?*View) void { + var target = _target; // While a layer surface is focused, views may not recieve focus if (self.focused == .layer) return; // If the view is not currently visible, behave as if null was passed - if (view) |v| { - if (v.output != self.focused_output or - v.pending.tags & self.focused_output.pending.tags == 0) view = null; + if (target) |view| { + if (view.output != self.focused_output or + view.pending.tags & self.focused_output.pending.tags == 0) target = null; } // If the target view is not fullscreen or null, then a fullscreen view // will grab focus if visible. - if (if (view) |v| !v.pending.fullscreen else true) { - var it = ViewStack(*View).pendingIterator(self.focus_stack.first, self.focused_output.pending.tags); - view = while (it.next()) |node| { - if (node.view.output == self.focused_output and node.view.pending.fullscreen) break node.view; - } else view; + if (if (target) |v| !v.pending.fullscreen else true) { + const tags = self.focused_output.pending.tags; + var it = ViewStack(*View).iter(self.focus_stack.first, .forward, tags, pendingFilter); + target = while (it.next()) |view| { + if (view.output == self.focused_output and view.pending.fullscreen) break view; + } else target; } - if (view == null) { + if (target == null) { // Set view to the first currently visible view in the focus stack if any - var it = ViewStack(*View).pendingIterator(self.focus_stack.first, self.focused_output.pending.tags); - view = while (it.next()) |node| { - if (node.view.output == self.focused_output) break node.view; + const tags = self.focused_output.pending.tags; + var it = ViewStack(*View).iter(self.focus_stack.first, .forward, tags, pendingFilter); + target = while (it.next()) |view| { + if (view.output == self.focused_output) break view; } else null; } - if (view) |view_to_focus| { + if (target) |view| { // Find or allocate a new node in the focus stack for the target view var it = self.focus_stack.first; while (it) |node| : (it = node.next) { // If the view is found, move it to the top of the stack - if (node.view == view_to_focus) { + if (node.view == view) { const new_focus_node = self.focus_stack.remove(node); self.focus_stack.push(node); break; @@ -153,18 +155,22 @@ pub fn focus(self: *Self, _view: ?*View) void { } else { // The view is not in the stack, so allocate a new node and prepend it const new_focus_node = util.gpa.create(ViewStack(*View).Node) catch return; - new_focus_node.view = view_to_focus; + new_focus_node.view = view; self.focus_stack.push(new_focus_node); } // Focus the target view - self.setFocusRaw(.{ .view = view_to_focus }); + self.setFocusRaw(.{ .view = view }); } else { // Otherwise clear the focus self.setFocusRaw(.{ .none = {} }); } } +fn pendingFilter(view: *View, filter_tags: u32) bool { + return !view.destroying and view.pending.tags & filter_tags != 0; +} + /// Switch focus to the target, handling unfocus and input inhibition /// properly. This should only be called directly if dealing with layers. pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void { diff --git a/river/command/focus_view.zig b/river/command/focus_view.zig index 4e77665..3ac3b12 100644 --- a/river/command/focus_view.zig +++ b/river/command/focus_view.zig @@ -44,15 +44,15 @@ pub fn focusView( // If there is a currently focused view, focus the next visible view in the stack. const focused_node = @fieldParentPtr(ViewStack(View).Node, "view", seat.focused.view); var it = switch (direction) { - .next => ViewStack(View).iterator(focused_node, output.current.tags), - .previous => ViewStack(View).reverseIterator(focused_node, output.current.tags), + .next => ViewStack(View).iter(focused_node, .forward, output.pending.tags, filter), + .previous => ViewStack(View).iter(focused_node, .reverse, output.pending.tags, filter), }; // Skip past the focused node _ = it.next(); // Focus the next visible node if there is one - if (it.next()) |node| { - seat.focus(&node.view); + if (it.next()) |view| { + seat.focus(view); output.root.startTransaction(); return; } @@ -61,10 +61,14 @@ pub fn focusView( // There is either no currently focused view or the last visible view in the // stack is focused and we need to wrap. var it = switch (direction) { - .next => ViewStack(View).iterator(output.views.first, output.current.tags), - .previous => ViewStack(View).reverseIterator(output.views.last, output.current.tags), + .next => ViewStack(View).iter(output.views.first, .forward, output.pending.tags, filter), + .previous => ViewStack(View).iter(output.views.last, .reverse, output.pending.tags, filter), }; - seat.focus(if (it.next()) |node| &node.view else null); + seat.focus(it.next()); output.root.startTransaction(); } + +fn filter(view: *View, filter_tags: u32) bool { + return !view.destroying and view.pending.tags & filter_tags != 0; +} diff --git a/river/command/zoom.zig b/river/command/zoom.zig index a701758..3efdc65 100644 --- a/river/command/zoom.zig +++ b/river/command/zoom.zig @@ -39,19 +39,14 @@ pub fn zoom( // If the first view that is part of the layout is focused, zoom // the next view in the layout. Otherwise zoom the focused view. const output = seat.focused_output; - var it = ViewStack(View).iterator(output.views.first, output.current.tags); - const layout_first = while (it.next()) |node| { - if (!node.view.pending.float and !node.view.pending.fullscreen) break node; - } else unreachable; + var it = ViewStack(View).iter(output.views.first, .forward, output.pending.tags, filter); + const layout_first = @fieldParentPtr(ViewStack(View).Node, "view", it.next().?); const focused_node = @fieldParentPtr(ViewStack(View).Node, "view", seat.focused.view); - const zoom_node = if (focused_node == layout_first) blk: { - while (it.next()) |node| { - if (!node.view.pending.float and !node.view.pending.fullscreen) break :blk node; - } else { - break :blk null; - } - } else focused_node; + const zoom_node = if (focused_node == layout_first) + if (it.next()) |view| @fieldParentPtr(ViewStack(View).Node, "view", view) else null + else + focused_node; if (zoom_node) |to_bump| { output.views.remove(to_bump); @@ -62,3 +57,8 @@ pub fn zoom( } } } + +fn filter(view: *View, filter_tags: u32) bool { + return !view.destroying and !view.pending.float and + !view.pending.fullscreen and view.pending.tags & filter_tags != 0; +} diff --git a/river/render.zig b/river/render.zig index 76492ab..9e3beaa 100644 --- a/river/render.zig +++ b/river/render.zig @@ -57,9 +57,9 @@ pub fn renderOutput(output: *Output) void { c.wlr_renderer_begin(wlr_renderer, width, height); // Find the first visible fullscreen view in the stack if there is one - var it = ViewStack(View).iterator(output.views.first, output.current.tags); - const fullscreen_view = while (it.next()) |node| { - if (node.view.current.fullscreen) break &node.view; + var it = ViewStack(View).iter(output.views.first, .forward, output.current.tags, renderFilter); + const fullscreen_view = while (it.next()) |view| { + if (view.current.fullscreen) break view; } else null; // If we have a fullscreen view to render, render it. @@ -76,14 +76,8 @@ pub fn renderOutput(output: *Output) void { renderLayer(output.*, output.layers[c.ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], &now); // The first view in the list is "on top" so iterate in reverse. - it = ViewStack(View).reverseIterator(output.views.last, output.current.tags); - while (it.next()) |node| { - const view = &node.view; - - // This check prevents a race condition when a frame is requested - // between mapping of a view and the first configure being handled. - if (view.current.box.width == 0 or view.current.box.height == 0) continue; - + it = ViewStack(View).iter(output.views.last, .reverse, output.current.tags, renderFilter); + while (it.next()) |view| { // Focused views are rendered on top of normal views, skip them for now if (view.current.focus != 0) continue; @@ -92,14 +86,8 @@ pub fn renderOutput(output: *Output) void { } // Render focused views - it = ViewStack(View).reverseIterator(output.views.last, output.current.tags); - while (it.next()) |node| { - const view = &node.view; - - // This check prevents a race condition when a frame is requested - // between mapping of a view and the first configure being handled. - if (view.current.box.width == 0 or view.current.box.height == 0) continue; - + it = ViewStack(View).iter(output.views.last, .reverse, output.current.tags, renderFilter); + while (it.next()) |view| { // Skip unfocused views since we already rendered them if (view.current.focus == 0) continue; @@ -130,6 +118,14 @@ pub fn renderOutput(output: *Output) void { _ = c.wlr_output_commit(output.wlr_output); } +fn renderFilter(view: *View, filter_tags: u32) bool { + // This check prevents a race condition when a frame is requested + // between mapping of a view and the first configure being handled. + if (view.current.box.width == 0 or view.current.box.height == 0) + return false; + return view.current.tags & filter_tags != 0; +} + /// Render all surfaces on the passed layer fn renderLayer(output: Output, layer: std.TailQueue(LayerSurface), now: *c.timespec) void { var it = layer.first; diff --git a/river/view_stack.zig b/river/view_stack.zig index eb69ebc..25f453e 100644 --- a/river/view_stack.zig +++ b/river/view_stack.zig @@ -41,14 +41,8 @@ pub fn ViewStack(comptime T: type) type { }; /// Top/bottom nodes in the stack - first: ?*Node, - last: ?*Node, - - /// Initialize an undefined stack - pub fn init(self: *Self) void { - self.first = null; - self.last = null; - } + first: ?*Node = null, + last: ?*Node = null, /// Add a node to the top of the stack. pub fn push(self: *Self, new_node: *Node) void { @@ -114,60 +108,42 @@ pub fn ViewStack(comptime T: type) type { } } - const Iterator = struct { - it: ?*Node, - tags: u32, - reverse: bool, - pending: bool, - - /// Returns the next node in iteration order, or null if done. - /// This function is horribly ugly, but it's well tested below. - pub fn next(self: *Iterator) ?*Node { - while (self.it) |node| : (self.it = if (self.reverse) node.prev else node.next) { - if (if (self.pending) - self.tags & node.view.pending.tags != 0 - else - self.tags & node.view.current.tags != 0) { - self.it = if (self.reverse) node.prev else node.next; - return node; - } - } - return null; - } + const Direction = enum { + forward, + reverse, }; - /// Returns an iterator starting at the passed node and filtered by - /// checking the passed tags against the current tags of each view. - pub fn iterator(start: ?*Node, tags: u32) Iterator { - return Iterator{ - .it = start, - .tags = tags, - .reverse = false, - .pending = false, + fn Iter(comptime Context: type) type { + return struct { + it: ?*Node, + dir: Direction, + context: Context, + filter: fn (*View, Context) bool, + + /// Returns the next node in iteration order which passes the + /// filter, or null if done. + pub fn next(self: *@This()) ?*View { + return while (self.it) |node| : (self.it = if (self.dir == .forward) node.next else node.prev) { + const view = if (T == View) &node.view else node.view; + if (self.filter(view, self.context)) { + self.it = if (self.dir == .forward) node.next else node.prev; + break view; + } + } else null; + } }; } - /// Returns a reverse iterator starting at the passed node and filtered by - /// checking the passed tags against the current tags of each view. - pub fn reverseIterator(start: ?*Node, tags: u32) Iterator { - return Iterator{ - .it = start, - .tags = tags, - .reverse = true, - .pending = false, - }; - } - - /// Returns an iterator starting at the passed node and filtered by - /// checking the passed tags against the pending tags of each view. - /// If a view has no pending tags, the current tags are used. - pub fn pendingIterator(start: ?*Node, tags: u32) Iterator { - return Iterator{ - .it = start, - .tags = tags, - .reverse = false, - .pending = true, - }; + /// Return a filtered iterator over the stack given a start node, + /// iteration direction, and filter function. Views for which the + /// filter function returns false will be skipped. + pub fn iter( + start: ?*Node, + dir: Direction, + context: var, + filter: fn (*View, @TypeOf(context)) bool, + ) Iter(@TypeOf(context)) { + return .{ .it = start, .dir = dir, .context = context, .filter = filter }; } }; } @@ -177,8 +153,7 @@ test "push/remove (*View)" { const allocator = testing.allocator; - var views: ViewStack(*View) = undefined; - views.init(); + var views = ViewStack(*View){}; const one = try allocator.create(ViewStack(*View).Node); defer allocator.destroy(one); @@ -309,8 +284,21 @@ test "iteration (View)" { const allocator = testing.allocator; - var views: ViewStack(View) = undefined; - views.init(); + const filters = struct { + fn all(view: *View, context: void) bool { + return true; + } + + fn none(view: *View, context: void) bool { + return false; + } + + fn current(view: *View, filter_tags: u32) bool { + return view.current.tags & filter_tags != 0; + } + }; + + var views = ViewStack(View){}; const one_a_pb = try allocator.create(ViewStack(View).Node); defer allocator.destroy(one_a_pb); @@ -343,94 +331,71 @@ test "iteration (View)" { views.push(five_b); // {5, 4, 1, 3} views.push(two_a); // {2, 5, 4, 1, 3} - // Iteration over all tags + // Iteration over all views { - var it = ViewStack(View).iterator(views.first, std.math.maxInt(u32)); - testing.expect((if (it.next()) |node| &node.view else null) == &two_a.view); - testing.expect((if (it.next()) |node| &node.view else null) == &five_b.view); - testing.expect((if (it.next()) |node| &node.view else null) == &four_b.view); - testing.expect((if (it.next()) |node| &node.view else null) == &one_a_pb.view); - testing.expect((if (it.next()) |node| &node.view else null) == &three_b_pa.view); + var it = ViewStack(View).iter(views.first, .forward, {}, filters.all); + testing.expect(it.next() == &two_a.view); + testing.expect(it.next() == &five_b.view); + testing.expect(it.next() == &four_b.view); + testing.expect(it.next() == &one_a_pb.view); + testing.expect(it.next() == &three_b_pa.view); + testing.expect(it.next() == null); + } + + // Iteration over no views + { + var it = ViewStack(View).iter(views.first, .forward, {}, filters.none); testing.expect(it.next() == null); } // Iteration over 'a' tags { - var it = ViewStack(View).iterator(views.first, 1 << 0); - testing.expect((if (it.next()) |node| &node.view else null) == &two_a.view); - testing.expect((if (it.next()) |node| &node.view else null) == &one_a_pb.view); + var it = ViewStack(View).iter(views.first, .forward, @as(u32, 1 << 0), filters.current); + testing.expect(it.next() == &two_a.view); + testing.expect(it.next() == &one_a_pb.view); testing.expect(it.next() == null); } // Iteration over 'b' tags { - var it = ViewStack(View).iterator(views.first, 1 << 1); - testing.expect((if (it.next()) |node| &node.view else null) == &five_b.view); - testing.expect((if (it.next()) |node| &node.view else null) == &four_b.view); - testing.expect((if (it.next()) |node| &node.view else null) == &three_b_pa.view); + var it = ViewStack(View).iter(views.first, .forward, @as(u32, 1 << 1), filters.current); + testing.expect(it.next() == &five_b.view); + testing.expect(it.next() == &four_b.view); + testing.expect(it.next() == &three_b_pa.view); testing.expect(it.next() == null); } - // Iteration over tags that aren't present + // Reverse iteration over all views { - var it = ViewStack(View).iterator(views.first, 1 << 2); + var it = ViewStack(View).iter(views.last, .reverse, {}, filters.all); + testing.expect(it.next() == &three_b_pa.view); + testing.expect(it.next() == &one_a_pb.view); + testing.expect(it.next() == &four_b.view); + testing.expect(it.next() == &five_b.view); + testing.expect(it.next() == &two_a.view); testing.expect(it.next() == null); } - // Reverse iteration over all tags + // Reverse iteration over no views { - var it = ViewStack(View).reverseIterator(views.last, std.math.maxInt(u32)); - testing.expect((if (it.next()) |node| &node.view else null) == &three_b_pa.view); - testing.expect((if (it.next()) |node| &node.view else null) == &one_a_pb.view); - testing.expect((if (it.next()) |node| &node.view else null) == &four_b.view); - testing.expect((if (it.next()) |node| &node.view else null) == &five_b.view); - testing.expect((if (it.next()) |node| &node.view else null) == &two_a.view); + var it = ViewStack(View).iter(views.last, .reverse, {}, filters.none); testing.expect(it.next() == null); } // Reverse iteration over 'a' tags { - var it = ViewStack(View).reverseIterator(views.last, 1 << 0); - testing.expect((if (it.next()) |node| &node.view else null) == &one_a_pb.view); - testing.expect((if (it.next()) |node| &node.view else null) == &two_a.view); + var it = ViewStack(View).iter(views.last, .reverse, @as(u32, 1 << 0), filters.current); + testing.expect(it.next() == &one_a_pb.view); + testing.expect(it.next() == &two_a.view); testing.expect(it.next() == null); } // Reverse iteration over 'b' tags { - var it = ViewStack(View).reverseIterator(views.last, 1 << 1); - testing.expect((if (it.next()) |node| &node.view else null) == &three_b_pa.view); - testing.expect((if (it.next()) |node| &node.view else null) == &four_b.view); - testing.expect((if (it.next()) |node| &node.view else null) == &five_b.view); - testing.expect(it.next() == null); - } - - // Reverse iteration over tags that aren't present - { - var it = ViewStack(View).reverseIterator(views.first, 1 << 2); - testing.expect(it.next() == null); - } - - // Iteration over (pending) 'a' tags - { - var it = ViewStack(View).pendingIterator(views.first, 1 << 0); - testing.expect((if (it.next()) |node| &node.view else null) == &two_a.view); - testing.expect((if (it.next()) |node| &node.view else null) == &three_b_pa.view); - testing.expect(it.next() == null); - } - - // Iteration over (pending) 'b' tags - { - var it = ViewStack(View).pendingIterator(views.first, 1 << 1); - testing.expect((if (it.next()) |node| &node.view else null) == &five_b.view); - testing.expect((if (it.next()) |node| &node.view else null) == &four_b.view); - testing.expect((if (it.next()) |node| &node.view else null) == &one_a_pb.view); - testing.expect(it.next() == null); - } - - // Iteration over (pending) tags that aren't present - { - var it = ViewStack(View).pendingIterator(views.first, 1 << 2); + var it = ViewStack(View).iter(views.last, .reverse, @as(u32, 1 << 1), filters.current); + testing.expect(it.next() == &three_b_pa.view); + testing.expect(it.next() == &four_b.view); + testing.expect(it.next() == &five_b.view); testing.expect(it.next() == null); } }