From 6cb9f6ac04e1fc7716bd69707c714ae89599cccc Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Fri, 3 Apr 2020 18:53:36 +0200 Subject: [PATCH] Add a data structure to manage the view stack --- build.zig | 18 ++- src/output.zig | 21 +-- src/root.zig | 142 ++++++++--------- src/server.zig | 5 +- src/test_main.zig | 3 + src/view.zig | 28 +--- src/view_stack.zig | 384 +++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 483 insertions(+), 118 deletions(-) create mode 100644 src/test_main.zig create mode 100644 src/view_stack.zig 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,97 +80,97 @@ 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; - } + 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) { - view.focus(view.wlr_xdg_surface.surface); - return; - } + 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(); } } - // 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| tags 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); + } +}