From db416eb11960bebd1d0802c1ee291ce0bb450ba8 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Fri, 21 Aug 2020 15:08:28 +0200 Subject: [PATCH] view stack: rework iteration for flexibility There is now a single iter() function which accepts a filter and context allowing users of the api to filter the views in any arbitrary way. This change allowed for a good amount of code cleanup, and this commit also ensures that the correct properties are checked in each case, including the new View.destroying field added in the previous commit. This fixes at least one crash involving switching focus to a destroying view. --- river/Cursor.zig | 18 +-- river/Output.zig | 39 +++---- river/OutputStatus.zig | 13 ++- river/Root.zig | 9 +- river/Seat.zig | 44 ++++---- river/command/focus_view.zig | 18 +-- river/command/zoom.zig | 22 ++-- river/render.zig | 34 +++--- river/view_stack.zig | 205 +++++++++++++++-------------------- 9 files changed, 185 insertions(+), 217 deletions(-) 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); } }