Add a data structure to manage the view stack

This commit is contained in:
Isaac Freund 2020-04-03 18:53:36 +02:00
parent 9ba295f126
commit 6cb9f6ac04
No known key found for this signature in database
GPG key ID: 86DED400DDFD7A11
7 changed files with 483 additions and 118 deletions

View file

@ -28,6 +28,22 @@ pub fn build(b: *Builder) void {
const run_cmd = exe.run(); const run_cmd = exe.run();
run_cmd.step.dependOn(b.getInstallStep()); run_cmd.step.dependOn(b.getInstallStep());
const run_step = b.step("run", "Run the app"); const run_step = b.step("run", "Run the compositor");
run_step.dependOn(&run_cmd.step); run_step.dependOn(&run_cmd.step);
const test_exe = b.addTest("src/test_main.zig");
test_exe.setTarget(target);
test_exe.setBuildMode(mode);
test_exe.addIncludeDir("protocol");
test_exe.linkLibC();
test_exe.addIncludeDir("/usr/include/pixman-1");
test_exe.addCSourceFile("include/render.c", &[_][]const u8{"-std=c99"});
test_exe.addIncludeDir(".");
//test_exe.linkSystemLibrary("pixman");
test_exe.linkSystemLibrary("wayland-server");
test_exe.linkSystemLibrary("wlroots");
test_exe.linkSystemLibrary("xkbcommon");
const test_step = b.step("test", "Run the tests");
test_step.dependOn(&test_exe.step);
} }

View file

@ -4,6 +4,7 @@ const c = @import("c.zig");
const Root = @import("root.zig").Root; const Root = @import("root.zig").Root;
const Server = @import("server.zig").Server; const Server = @import("server.zig").Server;
const View = @import("view.zig").View; const View = @import("view.zig").View;
const ViewStack = @import("view_stack.zig").ViewStack;
const RenderData = struct { const RenderData = struct {
output: *c.wlr_output, output: *c.wlr_output,
@ -79,22 +80,12 @@ pub const Output = struct {
const color = [_]f32{ 0.3, 0.3, 0.3, 1.0 }; const color = [_]f32{ 0.3, 0.3, 0.3, 1.0 };
c.wlr_renderer_clear(renderer, &color); c.wlr_renderer_clear(renderer, &color);
// Each subsequent view is rendered on top of the last.
// 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.
var it = output.root.views.last; var it = ViewStack.reverseIterator(
while (it) |node| : (it = node.prev) { output.root.views.first,
const view = &node.data; output.root.current_focused_tags,
);
// Only render currently visible views while (it.next()) |view| {
if (view.current_tags & output.root.current_focused_tags == 0) {
continue;
}
// TODO: remove this check and move unmaped views back to unmaped TailQueue
if (!view.mapped) {
// An unmapped view should not be rendered.
continue;
}
output.renderView(view, &now); output.renderView(view, &now);
} }

View file

@ -7,6 +7,7 @@ const Output = @import("output.zig").Output;
const Server = @import("server.zig").Server; const Server = @import("server.zig").Server;
const Seat = @import("seat.zig").Seat; const Seat = @import("seat.zig").Seat;
const View = @import("view.zig").View; const View = @import("view.zig").View;
const ViewStack = @import("view_stack.zig").ViewStack;
/// Responsible for all windowing operations /// Responsible for all windowing operations
pub const Root = struct { pub const Root = struct {
@ -17,10 +18,10 @@ pub const Root = struct {
wlr_output_layout: *c.wlr_output_layout, wlr_output_layout: *c.wlr_output_layout,
outputs: std.TailQueue(Output), outputs: std.TailQueue(Output),
// Must stay ordered, first N views in list are the masters /// The top of the stack is the "most important" view.
views: std.TailQueue(View), views: ViewStack,
unmapped_views: std.TailQueue(View),
/// The view that has seat focus, if any.
focused_view: ?*View, focused_view: ?*View,
/// A bit field of focused tags /// A bit field of focused tags
@ -33,8 +34,8 @@ pub const Root = struct {
/// Percentage of the total screen that the master section takes up. /// Percentage of the total screen that the master section takes up.
master_factor: f64, master_factor: f64,
// Number of pending configures sent in the current transaction. /// Number of pending configures sent in the current transaction.
// A value of 0 means there is no current transaction. /// A value of 0 means there is no current transaction.
pending_configures: u32, pending_configures: u32,
/// Handles timeout of transactions /// Handles timeout of transactions
@ -51,8 +52,7 @@ pub const Root = struct {
self.outputs = std.TailQueue(Output).init(); self.outputs = std.TailQueue(Output).init();
self.views = std.TailQueue(View).init(); self.views.init();
self.unmapped_views = std.TailQueue(View).init();
self.focused_view = null; self.focused_view = null;
@ -80,97 +80,97 @@ pub const Root = struct {
} }
pub fn addView(self: *Self, wlr_xdg_surface: *c.wlr_xdg_surface) void { pub fn addView(self: *Self, wlr_xdg_surface: *c.wlr_xdg_surface) void {
const node = self.views.allocateNode(self.server.allocator) catch unreachable; const node = self.server.allocator.create(ViewStack.Node) catch unreachable;
node.data.init(self, wlr_xdg_surface, self.current_focused_tags); node.view.init(self, wlr_xdg_surface, self.current_focused_tags);
self.unmapped_views.prepend(node); self.views.push(node);
} }
/// Finds the topmost view under the output layout coordinates lx, ly /// Finds the topmost view under the output layout coordinates lx, ly
/// returns the view if found, and a pointer to the wlr_surface as well as the surface coordinates /// returns the view if found, and a pointer to the wlr_surface as well as the surface coordinates
pub fn viewAt(self: Self, lx: f64, ly: f64, surface: *?*c.wlr_surface, sx: *f64, sy: *f64) ?*View { pub fn viewAt(self: Self, lx: f64, ly: f64, surface: *?*c.wlr_surface, sx: *f64, sy: *f64) ?*View {
var it = self.views.last; var it = ViewStack.iterator(self.views.first, 0xFFFFFFFF);
while (it) |node| : (it = node.prev) { while (it.next()) |view| {
if (node.data.isAt(lx, ly, surface, sx, sy)) { if (view.isAt(lx, ly, surface, sx, sy)) {
return &node.data; return view;
} }
} }
return null; return null;
} }
/// Clear the current focus.
pub fn clearFocus(self: *Self) void {
if (self.focused_view) |view| {
_ = c.wlr_xdg_toplevel_set_activated(view.wlr_xdg_surface, false);
}
self.focused_view = null;
}
/// Focus the next visible view in the stack, wrapping if needed. Does /// Focus the next visible view in the stack, wrapping if needed. Does
/// nothing if there is only one view in the stack. /// nothing if there is only one view in the stack.
pub fn focusNextView(self: Self) void { pub fn focusNextView(self: *Self) void {
if (self.focused_view) |current_focus| { if (self.focused_view) |current_focus| {
// 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.
var it = @fieldParentPtr(std.TailQueue(View).Node, "data", current_focus).next; const current_node = @fieldParentPtr(ViewStack.Node, "view", current_focus);
while (it) |node| : (it = node.next) { var it = ViewStack.iterator(current_node, self.current_focused_tags);
const view = &node.data; // Skip past the current node
if (view.current_tags & self.current_focused_tags != 0) { _ = it.next();
view.focus(view.wlr_xdg_surface.surface); // Focus the next visible node if there is one
return; if (it.next()) |view| {
} view.focus(view.wlr_xdg_surface.surface);
return;
} }
} }
// 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 = self.views.first; var it = ViewStack.iterator(self.views.first, self.current_focused_tags);
while (it) |node| : (it = node.next) { if (it.next()) |view| {
const view = &node.data; view.focus(view.wlr_xdg_surface.surface);
if (view.current_tags & self.current_focused_tags != 0) { } else {
view.focus(view.wlr_xdg_surface.surface); // Otherwise clear the focus since there are no visible views
return; self.clearFocus();
}
} }
} }
/// Focus the previous view in the stack, wrapping if needed. Does nothing /// Focus the previous view in the stack, wrapping if needed. Does nothing
/// if there is only one view in the stack. /// if there is only one view in the stack.
pub fn focusPrevView(self: Self) void { pub fn focusPrevView(self: *Self) void {
if (self.focused_view) |current_focus| { if (self.focused_view) |current_focus| {
// If there is a currently focused view, focus the previous visible view in the stack. // If there is a currently focused view, focus the previous visible view in the stack.
var it = @fieldParentPtr(std.TailQueue(View).Node, "data", current_focus).prev; const current_node = @fieldParentPtr(ViewStack.Node, "view", current_focus);
while (it) |node| : (it = node.prev) { var it = ViewStack.reverseIterator(current_node, self.current_focused_tags);
const view = &node.data; // Skip past the current node
if (view.current_tags & self.current_focused_tags != 0) { _ = it.next();
view.focus(view.wlr_xdg_surface.surface); // Focus the previous visible node if there is one
return; if (it.next()) |view| {
} view.focus(view.wlr_xdg_surface.surface);
return;
} }
} }
// There is either no currently focused view or the first visible view in the // There is either no currently focused view or the first visible view in the
// stack is focused and we need to wrap. // stack is focused and we need to wrap.
var it = self.views.last; var it = ViewStack.reverseIterator(self.views.last, self.current_focused_tags);
while (it) |node| : (it = node.prev) { if (it.next()) |view| {
const view = &node.data; view.focus(view.wlr_xdg_surface.surface);
if (view.current_tags & self.current_focused_tags != 0) { } else {
view.focus(view.wlr_xdg_surface.surface); // Otherwise clear the focus since there are no visible views
return; self.clearFocus();
}
} }
} }
// TODO: obsolete this function by using a better data structure
fn visibleCount(self: Self, tags: u32) u32 {
var count: u32 = 0;
var it = self.views.first;
while (it) |node| : (it = node.next) {
const view = &node.data;
if (view.current_tags & tags != 0) {
count += 1;
}
}
return count;
}
pub fn arrange(self: *Self) void { pub fn arrange(self: *Self) void {
const root_tags = if (self.pending_focused_tags) |tags| const root_tags = if (self.pending_focused_tags) |tags|
tags tags
else else
self.current_focused_tags; self.current_focused_tags;
const visible_count = self.visibleCount(root_tags); const visible_count = blk: {
var count: u32 = 0;
var it = ViewStack.pendingIterator(self.views.first, root_tags);
while (it.next() != null) count += 1;
break :blk count;
};
const master_count = util.min(u32, self.master_count, visible_count); const master_count = util.min(u32, self.master_count, visible_count);
const slave_count = if (master_count >= visible_count) 0 else visible_count - master_count; const slave_count = if (master_count >= visible_count) 0 else visible_count - master_count;
@ -192,18 +192,8 @@ pub const Root = struct {
} }
var i: u32 = 0; var i: u32 = 0;
var it = self.views.first; var it = ViewStack.pendingIterator(self.views.first, root_tags);
while (it) |node| : (it = node.next) { while (it.next()) |view| {
const view = &node.data;
if (view.pending_tags) |tags| {
if (root_tags & tags == 0) {
continue;
}
} else if (view.current_tags & root_tags == 0) {
continue;
}
if (i < master_count) { if (i < master_count) {
// Add the remainder to the first master to ensure every pixel of height is used // Add the remainder to the first master to ensure every pixel of height is used
const master_height = @divTrunc(@intCast(u32, output_box.height), master_count); const master_height = @divTrunc(@intCast(u32, output_box.height), master_count);
@ -246,10 +236,8 @@ pub const Root = struct {
// 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;
var it = self.views.first; var it = ViewStack.iterator(self.views.first, 0xFFFFFFFF);
while (it) |node| : (it = node.next) { while (it.next()) |view| {
const view = &node.data;
// Clear the serial in case this transaction is interrupting a prior one. // Clear the serial in case this transaction is interrupting a prior one.
view.pending_serial = null; view.pending_serial = null;
@ -322,10 +310,8 @@ pub const Root = struct {
self.pending_focused_tags = null; self.pending_focused_tags = null;
} }
var it = self.views.first; var it = ViewStack.iterator(self.views.first, 0xFFFFFFFF);
while (it) |node| : (it = node.next) { while (it.next()) |view| {
const view = &node.data;
// Ensure that all pending state is cleared // Ensure that all pending state is cleared
view.pending_serial = null; view.pending_serial = null;
if (view.pending_box) |state| { if (view.pending_box) |state| {

View file

@ -7,6 +7,7 @@ const Output = @import("output.zig").Output;
const Root = @import("root.zig").Root; const Root = @import("root.zig").Root;
const Seat = @import("seat.zig").Seat; const Seat = @import("seat.zig").Seat;
const View = @import("view.zig").View; const View = @import("view.zig").View;
const ViewStack = @import("view_stack.zig").ViewStack;
pub const Server = struct { pub const Server = struct {
const Self = @This(); const Self = @This();
@ -193,10 +194,10 @@ pub const Server = struct {
}, },
c.XKB_KEY_Return => { c.XKB_KEY_Return => {
if (self.root.focused_view) |current_focus| { if (self.root.focused_view) |current_focus| {
const node = @fieldParentPtr(std.TailQueue(View).Node, "data", current_focus); const node = @fieldParentPtr(ViewStack.Node, "view", current_focus);
if (node != self.root.views.first) { if (node != self.root.views.first) {
self.root.views.remove(node); self.root.views.remove(node);
self.root.views.prepend(node); self.root.views.push(node);
self.root.arrange(); self.root.arrange();
} }
} }

3
src/test_main.zig Normal file
View file

@ -0,0 +1,3 @@
test "river test suite" {
_ = @import("view_stack.zig");
}

View file

@ -2,6 +2,7 @@ const std = @import("std");
const c = @import("c.zig"); const c = @import("c.zig");
const Root = @import("root.zig").Root; const Root = @import("root.zig").Root;
const ViewStack = @import("view_stack.zig").ViewStack;
pub const View = struct { pub const View = struct {
const Self = @This(); const Self = @This();
@ -127,13 +128,7 @@ pub const View = struct {
// Called when the surface is mapped, or ready to display on-screen. // Called when the surface is mapped, or ready to display on-screen.
const view = @fieldParentPtr(View, "listen_map", listener.?); const view = @fieldParentPtr(View, "listen_map", listener.?);
view.mapped = true; view.mapped = true;
view.focus(view.wlr_xdg_surface.surface); view.focus(view.wlr_xdg_surface.surface);
const node = @fieldParentPtr(std.TailQueue(View).Node, "data", view);
view.root.unmapped_views.remove(node);
view.root.views.prepend(node);
view.root.arrange(); view.root.arrange();
} }
@ -145,22 +140,11 @@ pub const View = struct {
if (root.focused_view) |current_focus| { if (root.focused_view) |current_focus| {
// If the view being unmapped is focused // If the view being unmapped is focused
if (current_focus == view) { if (current_focus == view) {
// If there are more views // Focus the previous view. This clears the focus if there are no visible views.
if (root.views.len > 1) { root.focusPrevView();
// Focus the previous view.
root.focusPrevView();
} else {
// Otherwise clear the focus
root.focused_view = null;
_ = c.wlr_xdg_toplevel_set_activated(view.wlr_xdg_surface, false);
}
} }
} }
const node = @fieldParentPtr(std.TailQueue(View).Node, "data", view);
root.views.remove(node);
root.unmapped_views.append(node);
root.arrange(); root.arrange();
} }
@ -168,9 +152,9 @@ pub const View = struct {
const view = @fieldParentPtr(View, "listen_destroy", listener.?); const view = @fieldParentPtr(View, "listen_destroy", listener.?);
const root = view.root; const root = view.root;
const node = @fieldParentPtr(std.TailQueue(View).Node, "data", view); const node = @fieldParentPtr(ViewStack.Node, "view", view);
root.unmapped_views.remove(node); root.views.remove(node);
root.unmapped_views.destroyNode(node, root.server.allocator); root.server.allocator.destroy(node);
} }
fn handleCommit(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleCommit(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {

384
src/view_stack.zig Normal file
View file

@ -0,0 +1,384 @@
const View = @import("view.zig").View;
/// A specialized doubly-linked stack that allows for filtered iteration
/// over the nodes
pub const ViewStack = struct {
const Self = @This();
pub const Node = struct {
/// Previous/next nodes in the stack
prev: ?*Node,
next: ?*Node,
/// The view stored in this node
view: View,
};
/// Top/bottom nodes in the stack
first: ?*Node,
last: ?*Node,
/// Total number of views
len: u32,
/// Initialize an undefined stack
pub fn init(self: *Self) void {
self.first = null;
self.last = null;
self.len = 0;
}
/// Add a node to the top of the stack.
pub fn push(self: *Self, new_node: *Node) void {
// Set the prev/next pointers of the new node
new_node.prev = null;
new_node.next = self.first;
if (self.first) |first| {
// If the list is not empty, set the prev pointer of the current
// first node to the new node.
first.prev = new_node;
} else {
// If the list is empty set the last pointer to the new node.
self.last = new_node;
}
// Set the first pointer to the new node and increment length
self.first = new_node;
self.len += 1;
}
/// Remove a node from the view stack. This removes it from the stack of
/// all views as well as the stack of visible ones.
pub fn remove(self: *Self, target_node: *Node) void {
// Set the previous node/list head to the next pointer
if (target_node.prev) |prev_node| {
prev_node.next = target_node.next;
} else {
self.first = target_node.next;
}
// Set the next node/list tail to the previous pointer
if (target_node.next) |next_node| {
next_node.prev = target_node.prev;
} else {
self.last = target_node.prev;
}
self.len -= 1;
}
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) ?*View {
while (self.it) |node| : (self.it = if (self.reverse) node.prev else node.next) {
if (node.view.mapped and if (self.pending)
if (node.view.pending_tags) |pending_tags|
self.tags & pending_tags != 0
else
self.tags & node.view.current_tags != 0
else
self.tags & node.view.current_tags != 0) {
const ret = &node.view;
self.it = if (self.reverse) node.prev else node.next;
return ret;
}
}
return null;
}
};
/// Returns an iterator starting at the passed node and filtered by
/// checking the passed tags against the current tags of each view.
/// Unmapped views are skipped.
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
/// checking the passed tags against the current tags of each view.
/// Unmapped views are skipped.
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. Unmapped
/// views are skipped.
pub fn pendingIterator(start: ?*Node, tags: u32) Iterator {
return Iterator{
.it = start,
.tags = tags,
.reverse = false,
.pending = true,
};
}
};
const testing = @import("std").testing;
test "push/remove" {
const allocator = testing.allocator;
var views: ViewStack = undefined;
views.init();
var one = try allocator.create(ViewStack.Node);
defer allocator.destroy(one);
var two = try allocator.create(ViewStack.Node);
defer allocator.destroy(two);
var three = try allocator.create(ViewStack.Node);
defer allocator.destroy(three);
var four = try allocator.create(ViewStack.Node);
defer allocator.destroy(four);
var five = try allocator.create(ViewStack.Node);
defer allocator.destroy(five);
testing.expect(views.len == 0);
views.push(three); // {3}
views.push(one); // {1, 3}
views.push(four); // {4, 1, 3}
views.push(five); // {5, 4, 1, 3}
views.push(two); // {2, 5, 4, 1, 3}
testing.expect(views.len == 5);
// Simple insertion
{
var it = views.first;
testing.expect(it == two);
it = it.?.next;
testing.expect(it == five);
it = it.?.next;
testing.expect(it == four);
it = it.?.next;
testing.expect(it == one);
it = it.?.next;
testing.expect(it == three);
it = it.?.next;
testing.expect(it == null);
testing.expect(views.len == 5);
testing.expect(views.first == two);
testing.expect(views.last == three);
}
// Removal of first
views.remove(two);
{
var it = views.first;
testing.expect(it == five);
it = it.?.next;
testing.expect(it == four);
it = it.?.next;
testing.expect(it == one);
it = it.?.next;
testing.expect(it == three);
it = it.?.next;
testing.expect(it == null);
testing.expect(views.len == 4);
testing.expect(views.first == five);
testing.expect(views.last == three);
}
// Removal of last
views.remove(three);
{
var it = views.first;
testing.expect(it == five);
it = it.?.next;
testing.expect(it == four);
it = it.?.next;
testing.expect(it == one);
it = it.?.next;
testing.expect(it == null);
testing.expect(views.len == 3);
testing.expect(views.first == five);
testing.expect(views.last == one);
}
// Remove from middle
views.remove(four);
{
var it = views.first;
testing.expect(it == five);
it = it.?.next;
testing.expect(it == one);
it = it.?.next;
testing.expect(it == null);
testing.expect(views.len == 2);
testing.expect(views.first == five);
testing.expect(views.last == one);
}
// Reinsertion
views.push(two);
views.push(three);
views.push(four);
{
var it = views.first;
testing.expect(it == four);
it = it.?.next;
testing.expect(it == three);
it = it.?.next;
testing.expect(it == two);
it = it.?.next;
testing.expect(it == five);
it = it.?.next;
testing.expect(it == one);
it = it.?.next;
testing.expect(it == null);
testing.expect(views.len == 5);
testing.expect(views.first == four);
testing.expect(views.last == one);
}
// Clear
views.remove(four);
views.remove(two);
views.remove(three);
views.remove(one);
views.remove(five);
testing.expect(views.first == null);
testing.expect(views.last == null);
testing.expect(views.len == 0);
}
test "iteration" {
const allocator = testing.allocator;
var views: ViewStack = undefined;
views.init();
var one_a_pb = try allocator.create(ViewStack.Node);
defer allocator.destroy(one_a_pb);
one_a_pb.view.mapped = true;
one_a_pb.view.current_tags = 1 << 0;
one_a_pb.view.pending_tags = 1 << 1;
var two_a = try allocator.create(ViewStack.Node);
defer allocator.destroy(two_a);
two_a.view.mapped = true;
two_a.view.current_tags = 1 << 0;
var three_b_pa = try allocator.create(ViewStack.Node);
defer allocator.destroy(three_b_pa);
three_b_pa.view.mapped = true;
three_b_pa.view.current_tags = 1 << 1;
three_b_pa.view.pending_tags = 1 << 0;
var four_b = try allocator.create(ViewStack.Node);
defer allocator.destroy(four_b);
four_b.view.mapped = true;
four_b.view.current_tags = 1 << 1;
var five_b = try allocator.create(ViewStack.Node);
defer allocator.destroy(five_b);
five_b.view.mapped = true;
five_b.view.current_tags = 1 << 1;
views.push(three_b_pa); // {3}
views.push(one_a_pb); // {1, 3}
views.push(four_b); // {4, 1, 3}
views.push(five_b); // {5, 4, 1, 3}
views.push(two_a); // {2, 5, 4, 1, 3}
// Iteration over all tags
{
var it = ViewStack.iterator(views.first, 0xFFFFFFFF);
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 'a' tags
{
var it = ViewStack.iterator(views.first, 1 << 0);
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.iterator(views.first, 1 << 1);
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);
}
// Reverse iteration over all tags
{
var it = ViewStack.reverseIterator(views.last, 0xFFFFFFFF);
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 'a' tags
{
var it = ViewStack.reverseIterator(views.last, 1 << 0);
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.reverseIterator(views.last, 1 << 1);
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);
}
// Iteration over (pending) 'a' tags
{
var it = ViewStack.pendingIterator(views.first, 1 << 0);
testing.expect(it.next() == &two_a.view);
testing.expect(it.next() == &three_b_pa.view);
testing.expect(it.next() == null);
}
// Iteration over (pending) 'b' tags
{
var it = ViewStack.pendingIterator(views.first, 1 << 1);
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() == null);
}
}