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:
parent
fa08d85c58
commit
db416eb119
@ -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;
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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),
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user