aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build.zig18
-rw-r--r--src/output.zig21
-rw-r--r--src/root.zig142
-rw-r--r--src/server.zig5
-rw-r--r--src/test_main.zig3
-rw-r--r--src/view.zig28
-rw-r--r--src/view_stack.zig384
7 files changed, 483 insertions, 118 deletions
diff --git a/build.zig b/build.zig
index a5e2fd9..b4b7558 100644
--- a/build.zig
+++ b/build.zig
@@ -28,6 +28,22 @@ pub fn build(b: *Builder) void {
const run_cmd = exe.run();
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);
+
+ 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);
}
diff --git a/src/output.zig b/src/output.zig
index d3b3ffe..936ee51 100644
--- a/src/output.zig
+++ b/src/output.zig
@@ -4,6 +4,7 @@ const c = @import("c.zig");
const Root = @import("root.zig").Root;
const Server = @import("server.zig").Server;
const View = @import("view.zig").View;
+const ViewStack = @import("view_stack.zig").ViewStack;
const RenderData = struct {
output: *c.wlr_output,
@@ -79,22 +80,12 @@ pub const Output = struct {
const color = [_]f32{ 0.3, 0.3, 0.3, 1.0 };
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.
- var it = output.root.views.last;
- while (it) |node| : (it = node.prev) {
- const view = &node.data;
-
- // Only render currently visible views
- 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;
- }
+ var it = ViewStack.reverseIterator(
+ output.root.views.first,
+ output.root.current_focused_tags,
+ );
+ while (it.next()) |view| {
output.renderView(view, &now);
}
diff --git a/src/root.zig b/src/root.zig
index de2bba0..14935b2 100644
--- a/src/root.zig
+++ b/src/root.zig
@@ -7,6 +7,7 @@ const Output = @import("output.zig").Output;
const Server = @import("server.zig").Server;
const Seat = @import("seat.zig").Seat;
const View = @import("view.zig").View;
+const ViewStack = @import("view_stack.zig").ViewStack;
/// Responsible for all windowing operations
pub const Root = struct {
@@ -17,10 +18,10 @@ pub const Root = struct {
wlr_output_layout: *c.wlr_output_layout,
outputs: std.TailQueue(Output),
- // Must stay ordered, first N views in list are the masters
- views: std.TailQueue(View),
- unmapped_views: std.TailQueue(View),
+ /// The top of the stack is the "most important" view.
+ views: ViewStack,
+ /// The view that has seat focus, if any.
focused_view: ?*View,
/// 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.
master_factor: f64,
- // Number of pending configures sent in the current transaction.
- // A value of 0 means there is no current transaction.
+ /// Number of pending configures sent in the current transaction.
+ /// A value of 0 means there is no current transaction.
pending_configures: u32,
/// Handles timeout of transactions
@@ -51,8 +52,7 @@ pub const Root = struct {
self.outputs = std.TailQueue(Output).init();
- self.views = std.TailQueue(View).init();
- self.unmapped_views = std.TailQueue(View).init();
+ self.views.init();
self.focused_view = null;
@@ -80,88 +80,83 @@ pub const Root = struct {
}
pub fn addView(self: *Self, wlr_xdg_surface: *c.wlr_xdg_surface) void {
- const node = self.views.allocateNode(self.server.allocator) catch unreachable;
- node.data.init(self, wlr_xdg_surface, self.current_focused_tags);
- self.unmapped_views.prepend(node);
+ const node = self.server.allocator.create(ViewStack.Node) catch unreachable;
+ node.view.init(self, wlr_xdg_surface, self.current_focused_tags);
+ self.views.push(node);
}
/// 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
pub fn viewAt(self: Self, lx: f64, ly: f64, surface: *?*c.wlr_surface, sx: *f64, sy: *f64) ?*View {
- var it = self.views.last;
- while (it) |node| : (it = node.prev) {
- if (node.data.isAt(lx, ly, surface, sx, sy)) {
- return &node.data;
+ var it = ViewStack.iterator(self.views.first, 0xFFFFFFFF);
+ while (it.next()) |view| {
+ if (view.isAt(lx, ly, surface, sx, sy)) {
+ return view;
}
}
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
/// 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 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;
- while (it) |node| : (it = node.next) {
- const view = &node.data;
- if (view.current_tags & self.current_focused_tags != 0) {
- view.focus(view.wlr_xdg_surface.surface);
- return;
- }
+ const current_node = @fieldParentPtr(ViewStack.Node, "view", current_focus);
+ var it = ViewStack.iterator(current_node, self.current_focused_tags);
+ // Skip past the current node
+ _ = it.next();
+ // Focus the next visible node if there is one
+ 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
// stack is focused and we need to wrap.
- var it = self.views.first;
- while (it) |node| : (it = node.next) {
- const view = &node.data;
- if (view.current_tags & self.current_focused_tags != 0) {
- view.focus(view.wlr_xdg_surface.surface);
- return;
- }
+ var it = ViewStack.iterator(self.views.first, self.current_focused_tags);
+ if (it.next()) |view| {
+ view.focus(view.wlr_xdg_surface.surface);
+ } else {
+ // Otherwise clear the focus since there are no visible views
+ self.clearFocus();
}
}
/// Focus the previous view in the stack, wrapping if needed. Does nothing
/// 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 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;
- while (it) |node| : (it = node.prev) {
- const view = &node.data;
- if (view.current_tags & self.current_focused_tags != 0) {
- view.focus(view.wlr_xdg_surface.surface);
- return;
- }
- }
- }
-
- // There is either no currently focused view or the first visible view in the
- // stack is focused and we need to wrap.
- var it = self.views.last;
- while (it) |node| : (it = node.prev) {
- const view = &node.data;
- if (view.current_tags & self.current_focused_tags != 0) {
+ const current_node = @fieldParentPtr(ViewStack.Node, "view", current_focus);
+ var it = ViewStack.reverseIterator(current_node, self.current_focused_tags);
+ // Skip past the current node
+ _ = it.next();
+ // Focus the previous visible node if there is one
+ if (it.next()) |view| {
view.focus(view.wlr_xdg_surface.surface);
return;
}
}
- }
- // 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;
- }
+ // There is either no currently focused view or the first visible view in the
+ // stack is focused and we need to wrap.
+ var it = ViewStack.reverseIterator(self.views.last, self.current_focused_tags);
+ if (it.next()) |view| {
+ view.focus(view.wlr_xdg_surface.surface);
+ } else {
+ // Otherwise clear the focus since there are no visible views
+ self.clearFocus();
}
- return count;
}
pub fn arrange(self: *Self) void {
@@ -170,7 +165,12 @@ pub const Root = struct {
else
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 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 it = self.views.first;
- while (it) |node| : (it = node.next) {
- const view = &node.data;
-
- if (view.pending_tags) |tags| {
- if (root_tags & tags == 0) {
- continue;
- }
- } else if (view.current_tags & root_tags == 0) {
- continue;
- }
-
+ var it = ViewStack.pendingIterator(self.views.first, root_tags);
+ while (it.next()) |view| {
if (i < master_count) {
// 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);
@@ -246,10 +236,8 @@ pub const Root = struct {
// to reset the pending count to 0 and clear serials from the views
self.pending_configures = 0;
- var it = self.views.first;
- while (it) |node| : (it = node.next) {
- const view = &node.data;
-
+ var it = ViewStack.iterator(self.views.first, 0xFFFFFFFF);
+ while (it.next()) |view| {
// Clear the serial in case this transaction is interrupting a prior one.
view.pending_serial = null;
@@ -322,10 +310,8 @@ pub const Root = struct {
self.pending_focused_tags = null;
}
- var it = self.views.first;
- while (it) |node| : (it = node.next) {
- const view = &node.data;
-
+ var it = ViewStack.iterator(self.views.first, 0xFFFFFFFF);
+ while (it.next()) |view| {
// Ensure that all pending state is cleared
view.pending_serial = null;
if (view.pending_box) |state| {
diff --git a/src/server.zig b/src/server.zig
index 3b6a9bc..0c8d4df 100644
--- a/src/server.zig
+++ b/src/server.zig
@@ -7,6 +7,7 @@ const Output = @import("output.zig").Output;
const Root = @import("root.zig").Root;
const Seat = @import("seat.zig").Seat;
const View = @import("view.zig").View;
+const ViewStack = @import("view_stack.zig").ViewStack;
pub const Server = struct {
const Self = @This();
@@ -193,10 +194,10 @@ pub const Server = struct {
},
c.XKB_KEY_Return => {
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) {
self.root.views.remove(node);
- self.root.views.prepend(node);
+ self.root.views.push(node);
self.root.arrange();
}
}
diff --git a/src/test_main.zig b/src/test_main.zig
new file mode 100644
index 0000000..15888fc
--- /dev/null
+++ b/src/test_main.zig
@@ -0,0 +1,3 @@
+test "river test suite" {
+ _ = @import("view_stack.zig");
+}
diff --git a/src/view.zig b/src/view.zig
index 8344a8b..40debb0 100644
--- a/src/view.zig
+++ b/src/view.zig
@@ -2,6 +2,7 @@ const std = @import("std");
const c = @import("c.zig");
const Root = @import("root.zig").Root;
+const ViewStack = @import("view_stack.zig").ViewStack;
pub const View = struct {
const Self = @This();
@@ -127,13 +128,7 @@ pub const View = struct {
// Called when the surface is mapped, or ready to display on-screen.
const view = @fieldParentPtr(View, "listen_map", listener.?);
view.mapped = true;
-
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();
}
@@ -145,22 +140,11 @@ pub const View = struct {
if (root.focused_view) |current_focus| {
// If the view being unmapped is focused
if (current_focus == view) {
- // If there are more views
- if (root.views.len > 1) {
- // 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);
- }
+ // Focus the previous view. This clears the focus if there are no visible views.
+ root.focusPrevView();
}
}
- const node = @fieldParentPtr(std.TailQueue(View).Node, "data", view);
- root.views.remove(node);
- root.unmapped_views.append(node);
-
root.arrange();
}
@@ -168,9 +152,9 @@ pub const View = struct {
const view = @fieldParentPtr(View, "listen_destroy", listener.?);
const root = view.root;
- const node = @fieldParentPtr(std.TailQueue(View).Node, "data", view);
- root.unmapped_views.remove(node);
- root.unmapped_views.destroyNode(node, root.server.allocator);
+ const node = @fieldParentPtr(ViewStack.Node, "view", view);
+ root.views.remove(node);
+ root.server.allocator.destroy(node);
}
fn handleCommit(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
diff --git a/src/view_stack.zig b/src/view_stack.zig
new file mode 100644
index 0000000..d544f32
--- /dev/null
+++ b/src/view_stack.zig
@@ -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);
+ }
+}