Add a data structure to manage the view stack
This commit is contained in:
parent
9ba295f126
commit
6cb9f6ac04
7 changed files with 483 additions and 118 deletions
18
build.zig
18
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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
130
src/root.zig
130
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,89 +80,84 @@ 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) {
|
||||
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) {
|
||||
var it = ViewStack.iterator(self.views.first, self.current_focused_tags);
|
||||
if (it.next()) |view| {
|
||||
view.focus(view.wlr_xdg_surface.surface);
|
||||
return;
|
||||
}
|
||||
} 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) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
var it = ViewStack.reverseIterator(self.views.last, self.current_focused_tags);
|
||||
if (it.next()) |view| {
|
||||
view.focus(view.wlr_xdg_surface.surface);
|
||||
return;
|
||||
} else {
|
||||
// Otherwise clear the focus since there are no visible views
|
||||
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 {
|
||||
const root_tags = if (self.pending_focused_tags) |tags|
|
||||
|
@ -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| {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
3
src/test_main.zig
Normal file
3
src/test_main.zig
Normal file
|
@ -0,0 +1,3 @@
|
|||
test "river test suite" {
|
||||
_ = @import("view_stack.zig");
|
||||
}
|
26
src/view.zig
26
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,21 +140,10 @@ 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.
|
||||
// Focus the previous view. This clears the focus if there are no visible views.
|
||||
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();
|
||||
}
|
||||
|
@ -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 {
|
||||
|
|
384
src/view_stack.zig
Normal file
384
src/view_stack.zig
Normal 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);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue