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();
|
const run_cmd = exe.run();
|
||||||
run_cmd.step.dependOn(b.getInstallStep());
|
run_cmd.step.dependOn(b.getInstallStep());
|
||||||
|
|
||||||
const run_step = b.step("run", "Run the app");
|
const run_step = b.step("run", "Run the compositor");
|
||||||
run_step.dependOn(&run_cmd.step);
|
run_step.dependOn(&run_cmd.step);
|
||||||
|
|
||||||
|
const test_exe = b.addTest("src/test_main.zig");
|
||||||
|
test_exe.setTarget(target);
|
||||||
|
test_exe.setBuildMode(mode);
|
||||||
|
test_exe.addIncludeDir("protocol");
|
||||||
|
test_exe.linkLibC();
|
||||||
|
test_exe.addIncludeDir("/usr/include/pixman-1");
|
||||||
|
test_exe.addCSourceFile("include/render.c", &[_][]const u8{"-std=c99"});
|
||||||
|
test_exe.addIncludeDir(".");
|
||||||
|
//test_exe.linkSystemLibrary("pixman");
|
||||||
|
test_exe.linkSystemLibrary("wayland-server");
|
||||||
|
test_exe.linkSystemLibrary("wlroots");
|
||||||
|
test_exe.linkSystemLibrary("xkbcommon");
|
||||||
|
|
||||||
|
const test_step = b.step("test", "Run the tests");
|
||||||
|
test_step.dependOn(&test_exe.step);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ const c = @import("c.zig");
|
||||||
const Root = @import("root.zig").Root;
|
const Root = @import("root.zig").Root;
|
||||||
const Server = @import("server.zig").Server;
|
const Server = @import("server.zig").Server;
|
||||||
const View = @import("view.zig").View;
|
const View = @import("view.zig").View;
|
||||||
|
const ViewStack = @import("view_stack.zig").ViewStack;
|
||||||
|
|
||||||
const RenderData = struct {
|
const RenderData = struct {
|
||||||
output: *c.wlr_output,
|
output: *c.wlr_output,
|
||||||
|
@ -79,22 +80,12 @@ pub const Output = struct {
|
||||||
const color = [_]f32{ 0.3, 0.3, 0.3, 1.0 };
|
const color = [_]f32{ 0.3, 0.3, 0.3, 1.0 };
|
||||||
c.wlr_renderer_clear(renderer, &color);
|
c.wlr_renderer_clear(renderer, &color);
|
||||||
|
|
||||||
// Each subsequent view is rendered on top of the last.
|
|
||||||
// The first view in the list is "on top" so iterate in reverse.
|
// The first view in the list is "on top" so iterate in reverse.
|
||||||
var it = output.root.views.last;
|
var it = ViewStack.reverseIterator(
|
||||||
while (it) |node| : (it = node.prev) {
|
output.root.views.first,
|
||||||
const view = &node.data;
|
output.root.current_focused_tags,
|
||||||
|
);
|
||||||
// Only render currently visible views
|
while (it.next()) |view| {
|
||||||
if (view.current_tags & output.root.current_focused_tags == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: remove this check and move unmaped views back to unmaped TailQueue
|
|
||||||
if (!view.mapped) {
|
|
||||||
// An unmapped view should not be rendered.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
output.renderView(view, &now);
|
output.renderView(view, &now);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
142
src/root.zig
142
src/root.zig
|
@ -7,6 +7,7 @@ const Output = @import("output.zig").Output;
|
||||||
const Server = @import("server.zig").Server;
|
const Server = @import("server.zig").Server;
|
||||||
const Seat = @import("seat.zig").Seat;
|
const Seat = @import("seat.zig").Seat;
|
||||||
const View = @import("view.zig").View;
|
const View = @import("view.zig").View;
|
||||||
|
const ViewStack = @import("view_stack.zig").ViewStack;
|
||||||
|
|
||||||
/// Responsible for all windowing operations
|
/// Responsible for all windowing operations
|
||||||
pub const Root = struct {
|
pub const Root = struct {
|
||||||
|
@ -17,10 +18,10 @@ pub const Root = struct {
|
||||||
wlr_output_layout: *c.wlr_output_layout,
|
wlr_output_layout: *c.wlr_output_layout,
|
||||||
outputs: std.TailQueue(Output),
|
outputs: std.TailQueue(Output),
|
||||||
|
|
||||||
// Must stay ordered, first N views in list are the masters
|
/// The top of the stack is the "most important" view.
|
||||||
views: std.TailQueue(View),
|
views: ViewStack,
|
||||||
unmapped_views: std.TailQueue(View),
|
|
||||||
|
|
||||||
|
/// The view that has seat focus, if any.
|
||||||
focused_view: ?*View,
|
focused_view: ?*View,
|
||||||
|
|
||||||
/// A bit field of focused tags
|
/// A bit field of focused tags
|
||||||
|
@ -33,8 +34,8 @@ pub const Root = struct {
|
||||||
/// Percentage of the total screen that the master section takes up.
|
/// Percentage of the total screen that the master section takes up.
|
||||||
master_factor: f64,
|
master_factor: f64,
|
||||||
|
|
||||||
// Number of pending configures sent in the current transaction.
|
/// Number of pending configures sent in the current transaction.
|
||||||
// A value of 0 means there is no current transaction.
|
/// A value of 0 means there is no current transaction.
|
||||||
pending_configures: u32,
|
pending_configures: u32,
|
||||||
|
|
||||||
/// Handles timeout of transactions
|
/// Handles timeout of transactions
|
||||||
|
@ -51,8 +52,7 @@ pub const Root = struct {
|
||||||
|
|
||||||
self.outputs = std.TailQueue(Output).init();
|
self.outputs = std.TailQueue(Output).init();
|
||||||
|
|
||||||
self.views = std.TailQueue(View).init();
|
self.views.init();
|
||||||
self.unmapped_views = std.TailQueue(View).init();
|
|
||||||
|
|
||||||
self.focused_view = null;
|
self.focused_view = null;
|
||||||
|
|
||||||
|
@ -80,97 +80,97 @@ pub const Root = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn addView(self: *Self, wlr_xdg_surface: *c.wlr_xdg_surface) void {
|
pub fn addView(self: *Self, wlr_xdg_surface: *c.wlr_xdg_surface) void {
|
||||||
const node = self.views.allocateNode(self.server.allocator) catch unreachable;
|
const node = self.server.allocator.create(ViewStack.Node) catch unreachable;
|
||||||
node.data.init(self, wlr_xdg_surface, self.current_focused_tags);
|
node.view.init(self, wlr_xdg_surface, self.current_focused_tags);
|
||||||
self.unmapped_views.prepend(node);
|
self.views.push(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finds the topmost view under the output layout coordinates lx, ly
|
/// Finds the topmost view under the output layout coordinates lx, ly
|
||||||
/// returns the view if found, and a pointer to the wlr_surface as well as the surface coordinates
|
/// returns the view if found, and a pointer to the wlr_surface as well as the surface coordinates
|
||||||
pub fn viewAt(self: Self, lx: f64, ly: f64, surface: *?*c.wlr_surface, sx: *f64, sy: *f64) ?*View {
|
pub fn viewAt(self: Self, lx: f64, ly: f64, surface: *?*c.wlr_surface, sx: *f64, sy: *f64) ?*View {
|
||||||
var it = self.views.last;
|
var it = ViewStack.iterator(self.views.first, 0xFFFFFFFF);
|
||||||
while (it) |node| : (it = node.prev) {
|
while (it.next()) |view| {
|
||||||
if (node.data.isAt(lx, ly, surface, sx, sy)) {
|
if (view.isAt(lx, ly, surface, sx, sy)) {
|
||||||
return &node.data;
|
return view;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Clear the current focus.
|
||||||
|
pub fn clearFocus(self: *Self) void {
|
||||||
|
if (self.focused_view) |view| {
|
||||||
|
_ = c.wlr_xdg_toplevel_set_activated(view.wlr_xdg_surface, false);
|
||||||
|
}
|
||||||
|
self.focused_view = null;
|
||||||
|
}
|
||||||
|
|
||||||
/// Focus the next visible view in the stack, wrapping if needed. Does
|
/// Focus the next visible view in the stack, wrapping if needed. Does
|
||||||
/// nothing if there is only one view in the stack.
|
/// nothing if there is only one view in the stack.
|
||||||
pub fn focusNextView(self: Self) void {
|
pub fn focusNextView(self: *Self) void {
|
||||||
if (self.focused_view) |current_focus| {
|
if (self.focused_view) |current_focus| {
|
||||||
// If there is a currently focused view, focus the next visible view in the stack.
|
// If there is a currently focused view, focus the next visible view in the stack.
|
||||||
var it = @fieldParentPtr(std.TailQueue(View).Node, "data", current_focus).next;
|
const current_node = @fieldParentPtr(ViewStack.Node, "view", current_focus);
|
||||||
while (it) |node| : (it = node.next) {
|
var it = ViewStack.iterator(current_node, self.current_focused_tags);
|
||||||
const view = &node.data;
|
// Skip past the current node
|
||||||
if (view.current_tags & self.current_focused_tags != 0) {
|
_ = it.next();
|
||||||
view.focus(view.wlr_xdg_surface.surface);
|
// Focus the next visible node if there is one
|
||||||
return;
|
if (it.next()) |view| {
|
||||||
}
|
view.focus(view.wlr_xdg_surface.surface);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// There is either no currently focused view or the last visible view in the
|
// There is either no currently focused view or the last visible view in the
|
||||||
// stack is focused and we need to wrap.
|
// stack is focused and we need to wrap.
|
||||||
var it = self.views.first;
|
var it = ViewStack.iterator(self.views.first, self.current_focused_tags);
|
||||||
while (it) |node| : (it = node.next) {
|
if (it.next()) |view| {
|
||||||
const view = &node.data;
|
view.focus(view.wlr_xdg_surface.surface);
|
||||||
if (view.current_tags & self.current_focused_tags != 0) {
|
} else {
|
||||||
view.focus(view.wlr_xdg_surface.surface);
|
// Otherwise clear the focus since there are no visible views
|
||||||
return;
|
self.clearFocus();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Focus the previous view in the stack, wrapping if needed. Does nothing
|
/// Focus the previous view in the stack, wrapping if needed. Does nothing
|
||||||
/// if there is only one view in the stack.
|
/// if there is only one view in the stack.
|
||||||
pub fn focusPrevView(self: Self) void {
|
pub fn focusPrevView(self: *Self) void {
|
||||||
if (self.focused_view) |current_focus| {
|
if (self.focused_view) |current_focus| {
|
||||||
// If there is a currently focused view, focus the previous visible view in the stack.
|
// If there is a currently focused view, focus the previous visible view in the stack.
|
||||||
var it = @fieldParentPtr(std.TailQueue(View).Node, "data", current_focus).prev;
|
const current_node = @fieldParentPtr(ViewStack.Node, "view", current_focus);
|
||||||
while (it) |node| : (it = node.prev) {
|
var it = ViewStack.reverseIterator(current_node, self.current_focused_tags);
|
||||||
const view = &node.data;
|
// Skip past the current node
|
||||||
if (view.current_tags & self.current_focused_tags != 0) {
|
_ = it.next();
|
||||||
view.focus(view.wlr_xdg_surface.surface);
|
// Focus the previous visible node if there is one
|
||||||
return;
|
if (it.next()) |view| {
|
||||||
}
|
view.focus(view.wlr_xdg_surface.surface);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// There is either no currently focused view or the first visible view in the
|
// There is either no currently focused view or the first visible view in the
|
||||||
// stack is focused and we need to wrap.
|
// stack is focused and we need to wrap.
|
||||||
var it = self.views.last;
|
var it = ViewStack.reverseIterator(self.views.last, self.current_focused_tags);
|
||||||
while (it) |node| : (it = node.prev) {
|
if (it.next()) |view| {
|
||||||
const view = &node.data;
|
view.focus(view.wlr_xdg_surface.surface);
|
||||||
if (view.current_tags & self.current_focused_tags != 0) {
|
} else {
|
||||||
view.focus(view.wlr_xdg_surface.surface);
|
// Otherwise clear the focus since there are no visible views
|
||||||
return;
|
self.clearFocus();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: obsolete this function by using a better data structure
|
|
||||||
fn visibleCount(self: Self, tags: u32) u32 {
|
|
||||||
var count: u32 = 0;
|
|
||||||
var it = self.views.first;
|
|
||||||
while (it) |node| : (it = node.next) {
|
|
||||||
const view = &node.data;
|
|
||||||
if (view.current_tags & tags != 0) {
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn arrange(self: *Self) void {
|
pub fn arrange(self: *Self) void {
|
||||||
const root_tags = if (self.pending_focused_tags) |tags|
|
const root_tags = if (self.pending_focused_tags) |tags|
|
||||||
tags
|
tags
|
||||||
else
|
else
|
||||||
self.current_focused_tags;
|
self.current_focused_tags;
|
||||||
|
|
||||||
const visible_count = self.visibleCount(root_tags);
|
const visible_count = blk: {
|
||||||
|
var count: u32 = 0;
|
||||||
|
var it = ViewStack.pendingIterator(self.views.first, root_tags);
|
||||||
|
while (it.next() != null) count += 1;
|
||||||
|
break :blk count;
|
||||||
|
};
|
||||||
|
|
||||||
const master_count = util.min(u32, self.master_count, visible_count);
|
const master_count = util.min(u32, self.master_count, visible_count);
|
||||||
const slave_count = if (master_count >= visible_count) 0 else visible_count - master_count;
|
const slave_count = if (master_count >= visible_count) 0 else visible_count - master_count;
|
||||||
|
@ -192,18 +192,8 @@ pub const Root = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var i: u32 = 0;
|
var i: u32 = 0;
|
||||||
var it = self.views.first;
|
var it = ViewStack.pendingIterator(self.views.first, root_tags);
|
||||||
while (it) |node| : (it = node.next) {
|
while (it.next()) |view| {
|
||||||
const view = &node.data;
|
|
||||||
|
|
||||||
if (view.pending_tags) |tags| {
|
|
||||||
if (root_tags & tags == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else if (view.current_tags & root_tags == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i < master_count) {
|
if (i < master_count) {
|
||||||
// Add the remainder to the first master to ensure every pixel of height is used
|
// Add the remainder to the first master to ensure every pixel of height is used
|
||||||
const master_height = @divTrunc(@intCast(u32, output_box.height), master_count);
|
const master_height = @divTrunc(@intCast(u32, output_box.height), master_count);
|
||||||
|
@ -246,10 +236,8 @@ pub const Root = struct {
|
||||||
// to reset the pending count to 0 and clear serials from the views
|
// to reset the pending count to 0 and clear serials from the views
|
||||||
self.pending_configures = 0;
|
self.pending_configures = 0;
|
||||||
|
|
||||||
var it = self.views.first;
|
var it = ViewStack.iterator(self.views.first, 0xFFFFFFFF);
|
||||||
while (it) |node| : (it = node.next) {
|
while (it.next()) |view| {
|
||||||
const view = &node.data;
|
|
||||||
|
|
||||||
// Clear the serial in case this transaction is interrupting a prior one.
|
// Clear the serial in case this transaction is interrupting a prior one.
|
||||||
view.pending_serial = null;
|
view.pending_serial = null;
|
||||||
|
|
||||||
|
@ -322,10 +310,8 @@ pub const Root = struct {
|
||||||
self.pending_focused_tags = null;
|
self.pending_focused_tags = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var it = self.views.first;
|
var it = ViewStack.iterator(self.views.first, 0xFFFFFFFF);
|
||||||
while (it) |node| : (it = node.next) {
|
while (it.next()) |view| {
|
||||||
const view = &node.data;
|
|
||||||
|
|
||||||
// Ensure that all pending state is cleared
|
// Ensure that all pending state is cleared
|
||||||
view.pending_serial = null;
|
view.pending_serial = null;
|
||||||
if (view.pending_box) |state| {
|
if (view.pending_box) |state| {
|
||||||
|
|
|
@ -7,6 +7,7 @@ const Output = @import("output.zig").Output;
|
||||||
const Root = @import("root.zig").Root;
|
const Root = @import("root.zig").Root;
|
||||||
const Seat = @import("seat.zig").Seat;
|
const Seat = @import("seat.zig").Seat;
|
||||||
const View = @import("view.zig").View;
|
const View = @import("view.zig").View;
|
||||||
|
const ViewStack = @import("view_stack.zig").ViewStack;
|
||||||
|
|
||||||
pub const Server = struct {
|
pub const Server = struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
@ -193,10 +194,10 @@ pub const Server = struct {
|
||||||
},
|
},
|
||||||
c.XKB_KEY_Return => {
|
c.XKB_KEY_Return => {
|
||||||
if (self.root.focused_view) |current_focus| {
|
if (self.root.focused_view) |current_focus| {
|
||||||
const node = @fieldParentPtr(std.TailQueue(View).Node, "data", current_focus);
|
const node = @fieldParentPtr(ViewStack.Node, "view", current_focus);
|
||||||
if (node != self.root.views.first) {
|
if (node != self.root.views.first) {
|
||||||
self.root.views.remove(node);
|
self.root.views.remove(node);
|
||||||
self.root.views.prepend(node);
|
self.root.views.push(node);
|
||||||
self.root.arrange();
|
self.root.arrange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
3
src/test_main.zig
Normal file
3
src/test_main.zig
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
test "river test suite" {
|
||||||
|
_ = @import("view_stack.zig");
|
||||||
|
}
|
28
src/view.zig
28
src/view.zig
|
@ -2,6 +2,7 @@ const std = @import("std");
|
||||||
const c = @import("c.zig");
|
const c = @import("c.zig");
|
||||||
|
|
||||||
const Root = @import("root.zig").Root;
|
const Root = @import("root.zig").Root;
|
||||||
|
const ViewStack = @import("view_stack.zig").ViewStack;
|
||||||
|
|
||||||
pub const View = struct {
|
pub const View = struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
@ -127,13 +128,7 @@ pub const View = struct {
|
||||||
// Called when the surface is mapped, or ready to display on-screen.
|
// Called when the surface is mapped, or ready to display on-screen.
|
||||||
const view = @fieldParentPtr(View, "listen_map", listener.?);
|
const view = @fieldParentPtr(View, "listen_map", listener.?);
|
||||||
view.mapped = true;
|
view.mapped = true;
|
||||||
|
|
||||||
view.focus(view.wlr_xdg_surface.surface);
|
view.focus(view.wlr_xdg_surface.surface);
|
||||||
|
|
||||||
const node = @fieldParentPtr(std.TailQueue(View).Node, "data", view);
|
|
||||||
view.root.unmapped_views.remove(node);
|
|
||||||
view.root.views.prepend(node);
|
|
||||||
|
|
||||||
view.root.arrange();
|
view.root.arrange();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,22 +140,11 @@ pub const View = struct {
|
||||||
if (root.focused_view) |current_focus| {
|
if (root.focused_view) |current_focus| {
|
||||||
// If the view being unmapped is focused
|
// If the view being unmapped is focused
|
||||||
if (current_focus == view) {
|
if (current_focus == view) {
|
||||||
// If there are more views
|
// Focus the previous view. This clears the focus if there are no visible views.
|
||||||
if (root.views.len > 1) {
|
root.focusPrevView();
|
||||||
// Focus the previous view.
|
|
||||||
root.focusPrevView();
|
|
||||||
} else {
|
|
||||||
// Otherwise clear the focus
|
|
||||||
root.focused_view = null;
|
|
||||||
_ = c.wlr_xdg_toplevel_set_activated(view.wlr_xdg_surface, false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const node = @fieldParentPtr(std.TailQueue(View).Node, "data", view);
|
|
||||||
root.views.remove(node);
|
|
||||||
root.unmapped_views.append(node);
|
|
||||||
|
|
||||||
root.arrange();
|
root.arrange();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,9 +152,9 @@ pub const View = struct {
|
||||||
const view = @fieldParentPtr(View, "listen_destroy", listener.?);
|
const view = @fieldParentPtr(View, "listen_destroy", listener.?);
|
||||||
const root = view.root;
|
const root = view.root;
|
||||||
|
|
||||||
const node = @fieldParentPtr(std.TailQueue(View).Node, "data", view);
|
const node = @fieldParentPtr(ViewStack.Node, "view", view);
|
||||||
root.unmapped_views.remove(node);
|
root.views.remove(node);
|
||||||
root.unmapped_views.destroyNode(node, root.server.allocator);
|
root.server.allocator.destroy(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleCommit(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
fn handleCommit(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||||
|
|
384
src/view_stack.zig
Normal file
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