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.
This commit is contained in:
Isaac Freund 2020-08-21 15:08:28 +02:00
parent fa08d85c58
commit db416eb119
9 changed files with 185 additions and 217 deletions

View file

@ -548,16 +548,20 @@ fn layerSurfaceAt(
/// Find the topmost visible view surface (incl. popups) at ox,oy. /// 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 { 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. // Focused views are rendered on top, so look for them first.
var it = ViewStack(View).iterator(output.views.first, output.current.tags); var it = ViewStack(View).iter(output.views.first, .forward, output.current.tags, surfaceAtFilter);
while (it.next()) |node| { while (it.next()) |view| {
if (node.view.current.focus == 0) continue; if (view.current.focus == 0) continue;
if (node.view.surfaceAt(ox, oy, sx, sy)) |found| return found; if (view.surfaceAt(ox, oy, sx, sy)) |found| return found;
} }
it = ViewStack(View).iterator(output.views.first, output.current.tags); it = ViewStack(View).iter(output.views.first, .forward, output.current.tags, surfaceAtFilter);
while (it.next()) |node| { while (it.next()) |view| {
if (node.view.surfaceAt(ox, oy, sx, sy)) |found| return found; if (view.surfaceAt(ox, oy, sx, sy)) |found| return found;
} }
return null; return null;
} }
fn surfaceAtFilter(view: *View, filter_tags: u32) bool {
return !view.destroying and view.current.tags & filter_tags != 0;
}

View file

@ -97,7 +97,7 @@ pub fn init(self: *Self, root: *Root, wlr_output: *c.wlr_output) !void {
layer.* = std.TailQueue(LayerSurface).init(); layer.* = std.TailQueue(LayerSurface).init();
} }
self.views.init(); self.views = ViewStack(View){};
self.current = .{ self.current = .{
.tags = 1 << 0, .tags = 1 << 0,
@ -184,14 +184,11 @@ fn layoutFull(self: *Self, visible_count: u32) void {
.height = self.usable_box.height - (2 * xy_offset), .height = self.usable_box.height - (2 * xy_offset),
}; };
var it = ViewStack(View).pendingIterator(self.views.first, self.pending.tags); var it = ViewStack(View).iter(self.views.first, .forward, self.pending.tags, arrangeFilter);
while (it.next()) |node| { while (it.next()) |view| {
const view = &node.view;
if (!view.pending.float and !view.pending.fullscreen) {
view.pending.box = full_box; view.pending.box = full_box;
view.applyConstraints(); view.applyConstraints();
} }
}
} }
const LayoutError = error{ const LayoutError = error{
@ -285,32 +282,28 @@ fn layoutExternal(self: *Self, visible_count: u32) !void {
// Apply window configuration to views // Apply window configuration to views
var i: u32 = 0; var i: u32 = 0;
var view_it = ViewStack(View).pendingIterator(self.views.first, self.pending.tags); var view_it = ViewStack(View).iter(self.views.first, .forward, self.pending.tags, arrangeFilter);
while (view_it.next()) |node| { while (view_it.next()) |view| : (i += 1) {
const view = &node.view;
if (!view.pending.float and !view.pending.fullscreen and !view.destroying) {
view.pending.box = view_boxen.items[i]; view.pending.box = view_boxen.items[i];
view.applyConstraints(); view.applyConstraints();
i += 1;
}
} }
} }
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 /// Arrange all views on the output for the current layout. Modifies only
/// pending state, the changes are not appplied until a transaction is started /// pending state, the changes are not appplied until a transaction is started
/// and completed. /// and completed.
pub fn arrangeViews(self: *Self) void { pub fn arrangeViews(self: *Self) void {
if (self == &self.root.noop_output) return; 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 // Count up views that will be arranged by the layout
var layout_count: u32 = 0; var layout_count: u32 = 0;
var it = ViewStack(View).pendingIterator(self.views.first, self.pending.tags); var it = ViewStack(View).iter(self.views.first, .forward, self.pending.tags, arrangeFilter);
while (it.next()) |node| { while (it.next() != null) layout_count += 1;
const view = &node.view;
if (!view.pending.float and !view.pending.fullscreen and !view.destroying) layout_count += 1;
}
// If the usable area has a zero dimension, trying to arrange the layout // If the usable area has a zero dimension, trying to arrange the layout
// would cause an underflow and is pointless anyway. // would cause an underflow and is pointless anyway.

View file

@ -60,13 +60,14 @@ pub fn sendViewTags(self: Self) void {
var view_tags = std.ArrayList(u32).init(util.gpa); var view_tags = std.ArrayList(u32).init(util.gpa);
defer view_tags.deinit(); defer view_tags.deinit();
var it = ViewStack(View).iterator(self.output.views.first, std.math.maxInt(u32)); var it = self.output.views.first;
while (it.next()) |node| while (it) |node| : (it = node.next) {
view_tags.append(node.view.current.tags) catch { view_tags.append(node.view.current.tags) catch {
c.wl_resource_post_no_memory(self.wl_resource); c.wl_resource_post_no_memory(self.wl_resource);
log.crit(.river_status, "out of memory", .{}); log.crit(.river_status, "out of memory", .{});
return; return;
}; };
}
var wl_array = c.wl_array{ var wl_array = c.wl_array{
.size = view_tags.items.len * @sizeOf(u32), .size = view_tags.items.len * @sizeOf(u32),

View file

@ -125,12 +125,11 @@ pub fn startTransaction(self: *Self) void {
// to reset the pending count to 0 and clear serials from the views // to reset the pending count to 0 and clear serials from the views
self.pending_configures = 0; 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; var output_it = self.outputs.first;
while (output_it) |node| : (output_it = node.next) { while (output_it) |output_node| : (output_it = output_node.next) {
const output = &node.data; var view_it = output_node.data.views.first;
var view_it = ViewStack(View).iterator(output.views.first, std.math.maxInt(u32)); while (view_it) |view_node| : (view_it = view_node.next) {
while (view_it.next()) |view_node| {
const view = &view_node.view; const view = &view_node.view;
if (view.destroying) { if (view.destroying) {

View file

@ -88,7 +88,7 @@ pub fn init(self: *Self, input_manager: *InputManager, name: [*:0]const u8) !voi
self.focused = .none; self.focused = .none;
self.focus_stack.init(); self.focus_stack = ViewStack(*View){};
self.status_trackers = std.SinglyLinkedList(SeatStatus).init(); 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. /// 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. /// If null is passed, the first visible view in the focus stack will be focused.
pub fn focus(self: *Self, _view: ?*View) void { pub fn focus(self: *Self, _target: ?*View) void {
var view = _view; var target = _target;
// While a layer surface is focused, views may not recieve focus // While a layer surface is focused, views may not recieve focus
if (self.focused == .layer) return; if (self.focused == .layer) return;
// If the view is not currently visible, behave as if null was passed // If the view is not currently visible, behave as if null was passed
if (view) |v| { if (target) |view| {
if (v.output != self.focused_output or if (view.output != self.focused_output or
v.pending.tags & self.focused_output.pending.tags == 0) view = null; view.pending.tags & self.focused_output.pending.tags == 0) target = null;
} }
// If the target view is not fullscreen or null, then a fullscreen view // If the target view is not fullscreen or null, then a fullscreen view
// will grab focus if visible. // will grab focus if visible.
if (if (view) |v| !v.pending.fullscreen else true) { if (if (target) |v| !v.pending.fullscreen else true) {
var it = ViewStack(*View).pendingIterator(self.focus_stack.first, self.focused_output.pending.tags); const tags = self.focused_output.pending.tags;
view = while (it.next()) |node| { var it = ViewStack(*View).iter(self.focus_stack.first, .forward, tags, pendingFilter);
if (node.view.output == self.focused_output and node.view.pending.fullscreen) break node.view; target = while (it.next()) |view| {
} else 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 // 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); const tags = self.focused_output.pending.tags;
view = while (it.next()) |node| { var it = ViewStack(*View).iter(self.focus_stack.first, .forward, tags, pendingFilter);
if (node.view.output == self.focused_output) break node.view; target = while (it.next()) |view| {
if (view.output == self.focused_output) break view;
} else null; } else null;
} }
if (view) |view_to_focus| { if (target) |view| {
// Find or allocate a new node in the focus stack for the target view // Find or allocate a new node in the focus stack for the target view
var it = self.focus_stack.first; var it = self.focus_stack.first;
while (it) |node| : (it = node.next) { while (it) |node| : (it = node.next) {
// If the view is found, move it to the top of the stack // 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); const new_focus_node = self.focus_stack.remove(node);
self.focus_stack.push(node); self.focus_stack.push(node);
break; break;
@ -153,18 +155,22 @@ pub fn focus(self: *Self, _view: ?*View) void {
} else { } else {
// The view is not in the stack, so allocate a new node and prepend it // 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; 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); self.focus_stack.push(new_focus_node);
} }
// Focus the target view // Focus the target view
self.setFocusRaw(.{ .view = view_to_focus }); self.setFocusRaw(.{ .view = view });
} else { } else {
// Otherwise clear the focus // Otherwise clear the focus
self.setFocusRaw(.{ .none = {} }); 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 /// Switch focus to the target, handling unfocus and input inhibition
/// properly. This should only be called directly if dealing with layers. /// properly. This should only be called directly if dealing with layers.
pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void { pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void {

View file

@ -44,15 +44,15 @@ pub fn focusView(
// If there is a currently focused view, focus the next visible view in the stack. // 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); const focused_node = @fieldParentPtr(ViewStack(View).Node, "view", seat.focused.view);
var it = switch (direction) { var it = switch (direction) {
.next => ViewStack(View).iterator(focused_node, output.current.tags), .next => ViewStack(View).iter(focused_node, .forward, output.pending.tags, filter),
.previous => ViewStack(View).reverseIterator(focused_node, output.current.tags), .previous => ViewStack(View).iter(focused_node, .reverse, output.pending.tags, filter),
}; };
// Skip past the focused node // Skip past the focused node
_ = it.next(); _ = it.next();
// Focus the next visible node if there is one // Focus the next visible node if there is one
if (it.next()) |node| { if (it.next()) |view| {
seat.focus(&node.view); seat.focus(view);
output.root.startTransaction(); output.root.startTransaction();
return; return;
} }
@ -61,10 +61,14 @@ pub fn focusView(
// There is either no currently focused view or the last visible view in the // There is either no currently focused view or the last visible view in the
// stack is focused and we need to wrap. // stack is focused and we need to wrap.
var it = switch (direction) { var it = switch (direction) {
.next => ViewStack(View).iterator(output.views.first, output.current.tags), .next => ViewStack(View).iter(output.views.first, .forward, output.pending.tags, filter),
.previous => ViewStack(View).reverseIterator(output.views.last, output.current.tags), .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(); output.root.startTransaction();
} }
fn filter(view: *View, filter_tags: u32) bool {
return !view.destroying and view.pending.tags & filter_tags != 0;
}

View file

@ -39,19 +39,14 @@ pub fn zoom(
// If the first view that is part of the layout is focused, 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. // the next view in the layout. Otherwise zoom the focused view.
const output = seat.focused_output; const output = seat.focused_output;
var it = ViewStack(View).iterator(output.views.first, output.current.tags); var it = ViewStack(View).iter(output.views.first, .forward, output.pending.tags, filter);
const layout_first = while (it.next()) |node| { const layout_first = @fieldParentPtr(ViewStack(View).Node, "view", it.next().?);
if (!node.view.pending.float and !node.view.pending.fullscreen) break node;
} else unreachable;
const focused_node = @fieldParentPtr(ViewStack(View).Node, "view", seat.focused.view); const focused_node = @fieldParentPtr(ViewStack(View).Node, "view", seat.focused.view);
const zoom_node = if (focused_node == layout_first) blk: { const zoom_node = if (focused_node == layout_first)
while (it.next()) |node| { if (it.next()) |view| @fieldParentPtr(ViewStack(View).Node, "view", view) else null
if (!node.view.pending.float and !node.view.pending.fullscreen) break :blk node; else
} else { focused_node;
break :blk null;
}
} else focused_node;
if (zoom_node) |to_bump| { if (zoom_node) |to_bump| {
output.views.remove(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;
}

View file

@ -57,9 +57,9 @@ pub fn renderOutput(output: *Output) void {
c.wlr_renderer_begin(wlr_renderer, width, height); c.wlr_renderer_begin(wlr_renderer, width, height);
// Find the first visible fullscreen view in the stack if there is one // Find the first visible fullscreen view in the stack if there is one
var it = ViewStack(View).iterator(output.views.first, output.current.tags); var it = ViewStack(View).iter(output.views.first, .forward, output.current.tags, renderFilter);
const fullscreen_view = while (it.next()) |node| { const fullscreen_view = while (it.next()) |view| {
if (node.view.current.fullscreen) break &node.view; if (view.current.fullscreen) break view;
} else null; } else null;
// If we have a fullscreen view to render, render it. // 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); 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. // The first view in the list is "on top" so iterate in reverse.
it = ViewStack(View).reverseIterator(output.views.last, output.current.tags); it = ViewStack(View).iter(output.views.last, .reverse, output.current.tags, renderFilter);
while (it.next()) |node| { while (it.next()) |view| {
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;
// Focused views are rendered on top of normal views, skip them for now // Focused views are rendered on top of normal views, skip them for now
if (view.current.focus != 0) continue; if (view.current.focus != 0) continue;
@ -92,14 +86,8 @@ pub fn renderOutput(output: *Output) void {
} }
// Render focused views // Render focused views
it = ViewStack(View).reverseIterator(output.views.last, output.current.tags); it = ViewStack(View).iter(output.views.last, .reverse, output.current.tags, renderFilter);
while (it.next()) |node| { while (it.next()) |view| {
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;
// Skip unfocused views since we already rendered them // Skip unfocused views since we already rendered them
if (view.current.focus == 0) continue; if (view.current.focus == 0) continue;
@ -130,6 +118,14 @@ pub fn renderOutput(output: *Output) void {
_ = c.wlr_output_commit(output.wlr_output); _ = 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 /// Render all surfaces on the passed layer
fn renderLayer(output: Output, layer: std.TailQueue(LayerSurface), now: *c.timespec) void { fn renderLayer(output: Output, layer: std.TailQueue(LayerSurface), now: *c.timespec) void {
var it = layer.first; var it = layer.first;

View file

@ -41,14 +41,8 @@ pub fn ViewStack(comptime T: type) type {
}; };
/// Top/bottom nodes in the stack /// Top/bottom nodes in the stack
first: ?*Node, first: ?*Node = null,
last: ?*Node, last: ?*Node = null,
/// Initialize an undefined stack
pub fn init(self: *Self) void {
self.first = null;
self.last = null;
}
/// Add a node to the top of the stack. /// Add a node to the top of the stack.
pub fn push(self: *Self, new_node: *Node) void { pub fn push(self: *Self, new_node: *Node) void {
@ -114,60 +108,42 @@ pub fn ViewStack(comptime T: type) type {
} }
} }
const Iterator = struct { const Direction = enum {
forward,
reverse,
};
fn Iter(comptime Context: type) type {
return struct {
it: ?*Node, it: ?*Node,
tags: u32, dir: Direction,
reverse: bool, context: Context,
pending: bool, filter: fn (*View, Context) bool,
/// Returns the next node in iteration order, or null if done. /// Returns the next node in iteration order which passes the
/// This function is horribly ugly, but it's well tested below. /// filter, or null if done.
pub fn next(self: *Iterator) ?*Node { pub fn next(self: *@This()) ?*View {
while (self.it) |node| : (self.it = if (self.reverse) node.prev else node.next) { return while (self.it) |node| : (self.it = if (self.dir == .forward) node.next else node.prev) {
if (if (self.pending) const view = if (T == View) &node.view else node.view;
self.tags & node.view.pending.tags != 0 if (self.filter(view, self.context)) {
else self.it = if (self.dir == .forward) node.next else node.prev;
self.tags & node.view.current.tags != 0) { break view;
self.it = if (self.reverse) node.prev else node.next;
return node;
} }
} else null;
} }
return null;
}
};
/// 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,
}; };
} }
/// Returns a reverse iterator starting at the passed node and filtered by /// Return a filtered iterator over the stack given a start node,
/// checking the passed tags against the current tags of each view. /// iteration direction, and filter function. Views for which the
pub fn reverseIterator(start: ?*Node, tags: u32) Iterator { /// filter function returns false will be skipped.
return Iterator{ pub fn iter(
.it = start, start: ?*Node,
.tags = tags, dir: Direction,
.reverse = true, context: var,
.pending = false, filter: fn (*View, @TypeOf(context)) bool,
}; ) Iter(@TypeOf(context)) {
} return .{ .it = start, .dir = dir, .context = context, .filter = filter };
/// 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,
};
} }
}; };
} }
@ -177,8 +153,7 @@ test "push/remove (*View)" {
const allocator = testing.allocator; const allocator = testing.allocator;
var views: ViewStack(*View) = undefined; var views = ViewStack(*View){};
views.init();
const one = try allocator.create(ViewStack(*View).Node); const one = try allocator.create(ViewStack(*View).Node);
defer allocator.destroy(one); defer allocator.destroy(one);
@ -309,8 +284,21 @@ test "iteration (View)" {
const allocator = testing.allocator; const allocator = testing.allocator;
var views: ViewStack(View) = undefined; const filters = struct {
views.init(); 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); const one_a_pb = try allocator.create(ViewStack(View).Node);
defer allocator.destroy(one_a_pb); defer allocator.destroy(one_a_pb);
@ -343,94 +331,71 @@ test "iteration (View)" {
views.push(five_b); // {5, 4, 1, 3} views.push(five_b); // {5, 4, 1, 3}
views.push(two_a); // {2, 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)); var it = ViewStack(View).iter(views.first, .forward, {}, filters.all);
testing.expect((if (it.next()) |node| &node.view else null) == &two_a.view); testing.expect(it.next() == &two_a.view);
testing.expect((if (it.next()) |node| &node.view else null) == &five_b.view); testing.expect(it.next() == &five_b.view);
testing.expect((if (it.next()) |node| &node.view else null) == &four_b.view); testing.expect(it.next() == &four_b.view);
testing.expect((if (it.next()) |node| &node.view else null) == &one_a_pb.view); testing.expect(it.next() == &one_a_pb.view);
testing.expect((if (it.next()) |node| &node.view else null) == &three_b_pa.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); testing.expect(it.next() == null);
} }
// Iteration over 'a' tags // Iteration over 'a' tags
{ {
var it = ViewStack(View).iterator(views.first, 1 << 0); var it = ViewStack(View).iter(views.first, .forward, @as(u32, 1 << 0), filters.current);
testing.expect((if (it.next()) |node| &node.view else null) == &two_a.view); testing.expect(it.next() == &two_a.view);
testing.expect((if (it.next()) |node| &node.view else null) == &one_a_pb.view); testing.expect(it.next() == &one_a_pb.view);
testing.expect(it.next() == null); testing.expect(it.next() == null);
} }
// Iteration over 'b' tags // Iteration over 'b' tags
{ {
var it = ViewStack(View).iterator(views.first, 1 << 1); var it = ViewStack(View).iter(views.first, .forward, @as(u32, 1 << 1), filters.current);
testing.expect((if (it.next()) |node| &node.view else null) == &five_b.view); testing.expect(it.next() == &five_b.view);
testing.expect((if (it.next()) |node| &node.view else null) == &four_b.view); testing.expect(it.next() == &four_b.view);
testing.expect((if (it.next()) |node| &node.view else null) == &three_b_pa.view); testing.expect(it.next() == &three_b_pa.view);
testing.expect(it.next() == null); 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); 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)); var it = ViewStack(View).iter(views.last, .reverse, {}, filters.none);
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);
testing.expect(it.next() == null); testing.expect(it.next() == null);
} }
// Reverse iteration over 'a' tags // Reverse iteration over 'a' tags
{ {
var it = ViewStack(View).reverseIterator(views.last, 1 << 0); var it = ViewStack(View).iter(views.last, .reverse, @as(u32, 1 << 0), filters.current);
testing.expect((if (it.next()) |node| &node.view else null) == &one_a_pb.view); testing.expect(it.next() == &one_a_pb.view);
testing.expect((if (it.next()) |node| &node.view else null) == &two_a.view); testing.expect(it.next() == &two_a.view);
testing.expect(it.next() == null); testing.expect(it.next() == null);
} }
// Reverse iteration over 'b' tags // Reverse iteration over 'b' tags
{ {
var it = ViewStack(View).reverseIterator(views.last, 1 << 1); var it = ViewStack(View).iter(views.last, .reverse, @as(u32, 1 << 1), filters.current);
testing.expect((if (it.next()) |node| &node.view else null) == &three_b_pa.view); testing.expect(it.next() == &three_b_pa.view);
testing.expect((if (it.next()) |node| &node.view else null) == &four_b.view); testing.expect(it.next() == &four_b.view);
testing.expect((if (it.next()) |node| &node.view else null) == &five_b.view); testing.expect(it.next() == &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);
testing.expect(it.next() == null); testing.expect(it.next() == null);
} }
} }