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.
|
/// 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 {
|
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.
|
// Focused views are rendered on top, so look for them first.
|
||||||
var it = ViewStack(View).iterator(output.views.first, output.current.tags);
|
var it = ViewStack(View).iter(output.views.first, .forward, output.current.tags, surfaceAtFilter);
|
||||||
while (it.next()) |node| {
|
while (it.next()) |view| {
|
||||||
if (node.view.current.focus == 0) continue;
|
if (view.current.focus == 0) continue;
|
||||||
if (node.view.surfaceAt(ox, oy, sx, sy)) |found| return found;
|
if (view.surfaceAt(ox, oy, sx, sy)) |found| return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
it = ViewStack(View).iterator(output.views.first, output.current.tags);
|
it = ViewStack(View).iter(output.views.first, .forward, output.current.tags, surfaceAtFilter);
|
||||||
while (it.next()) |node| {
|
while (it.next()) |view| {
|
||||||
if (node.view.surfaceAt(ox, oy, sx, sy)) |found| return found;
|
if (view.surfaceAt(ox, oy, sx, sy)) |found| return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
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();
|
layer.* = std.TailQueue(LayerSurface).init();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.views.init();
|
self.views = ViewStack(View){};
|
||||||
|
|
||||||
self.current = .{
|
self.current = .{
|
||||||
.tags = 1 << 0,
|
.tags = 1 << 0,
|
||||||
@ -184,14 +184,11 @@ fn layoutFull(self: *Self, visible_count: u32) void {
|
|||||||
.height = self.usable_box.height - (2 * xy_offset),
|
.height = self.usable_box.height - (2 * xy_offset),
|
||||||
};
|
};
|
||||||
|
|
||||||
var it = ViewStack(View).pendingIterator(self.views.first, self.pending.tags);
|
var it = ViewStack(View).iter(self.views.first, .forward, self.pending.tags, arrangeFilter);
|
||||||
while (it.next()) |node| {
|
while (it.next()) |view| {
|
||||||
const view = &node.view;
|
|
||||||
if (!view.pending.float and !view.pending.fullscreen) {
|
|
||||||
view.pending.box = full_box;
|
view.pending.box = full_box;
|
||||||
view.applyConstraints();
|
view.applyConstraints();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const LayoutError = error{
|
const LayoutError = error{
|
||||||
@ -285,32 +282,28 @@ fn layoutExternal(self: *Self, visible_count: u32) !void {
|
|||||||
|
|
||||||
// Apply window configuration to views
|
// Apply window configuration to views
|
||||||
var i: u32 = 0;
|
var i: u32 = 0;
|
||||||
var view_it = ViewStack(View).pendingIterator(self.views.first, self.pending.tags);
|
var view_it = ViewStack(View).iter(self.views.first, .forward, self.pending.tags, arrangeFilter);
|
||||||
while (view_it.next()) |node| {
|
while (view_it.next()) |view| : (i += 1) {
|
||||||
const view = &node.view;
|
|
||||||
if (!view.pending.float and !view.pending.fullscreen and !view.destroying) {
|
|
||||||
view.pending.box = view_boxen.items[i];
|
view.pending.box = view_boxen.items[i];
|
||||||
view.applyConstraints();
|
view.applyConstraints();
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
/// Arrange all views on the output for the current layout. Modifies only
|
||||||
/// pending state, the changes are not appplied until a transaction is started
|
/// pending state, the changes are not appplied until a transaction is started
|
||||||
/// and completed.
|
/// and completed.
|
||||||
pub fn arrangeViews(self: *Self) void {
|
pub fn arrangeViews(self: *Self) void {
|
||||||
if (self == &self.root.noop_output) return;
|
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
|
// Count up views that will be arranged by the layout
|
||||||
var layout_count: u32 = 0;
|
var layout_count: u32 = 0;
|
||||||
var it = ViewStack(View).pendingIterator(self.views.first, self.pending.tags);
|
var it = ViewStack(View).iter(self.views.first, .forward, self.pending.tags, arrangeFilter);
|
||||||
while (it.next()) |node| {
|
while (it.next() != null) layout_count += 1;
|
||||||
const view = &node.view;
|
|
||||||
if (!view.pending.float and !view.pending.fullscreen and !view.destroying) layout_count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the usable area has a zero dimension, trying to arrange the layout
|
// If the usable area has a zero dimension, trying to arrange the layout
|
||||||
// would cause an underflow and is pointless anyway.
|
// 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);
|
var view_tags = std.ArrayList(u32).init(util.gpa);
|
||||||
defer view_tags.deinit();
|
defer view_tags.deinit();
|
||||||
|
|
||||||
var it = ViewStack(View).iterator(self.output.views.first, std.math.maxInt(u32));
|
var it = self.output.views.first;
|
||||||
while (it.next()) |node|
|
while (it) |node| : (it = node.next) {
|
||||||
view_tags.append(node.view.current.tags) catch {
|
view_tags.append(node.view.current.tags) catch {
|
||||||
c.wl_resource_post_no_memory(self.wl_resource);
|
c.wl_resource_post_no_memory(self.wl_resource);
|
||||||
log.crit(.river_status, "out of memory", .{});
|
log.crit(.river_status, "out of memory", .{});
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
var wl_array = c.wl_array{
|
var wl_array = c.wl_array{
|
||||||
.size = view_tags.items.len * @sizeOf(u32),
|
.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
|
// to reset the pending count to 0 and clear serials from the views
|
||||||
self.pending_configures = 0;
|
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;
|
var output_it = self.outputs.first;
|
||||||
while (output_it) |node| : (output_it = node.next) {
|
while (output_it) |output_node| : (output_it = output_node.next) {
|
||||||
const output = &node.data;
|
var view_it = output_node.data.views.first;
|
||||||
var view_it = ViewStack(View).iterator(output.views.first, std.math.maxInt(u32));
|
while (view_it) |view_node| : (view_it = view_node.next) {
|
||||||
while (view_it.next()) |view_node| {
|
|
||||||
const view = &view_node.view;
|
const view = &view_node.view;
|
||||||
|
|
||||||
if (view.destroying) {
|
if (view.destroying) {
|
||||||
|
@ -88,7 +88,7 @@ pub fn init(self: *Self, input_manager: *InputManager, name: [*:0]const u8) !voi
|
|||||||
|
|
||||||
self.focused = .none;
|
self.focused = .none;
|
||||||
|
|
||||||
self.focus_stack.init();
|
self.focus_stack = ViewStack(*View){};
|
||||||
|
|
||||||
self.status_trackers = std.SinglyLinkedList(SeatStatus).init();
|
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.
|
/// 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.
|
/// If null is passed, the first visible view in the focus stack will be focused.
|
||||||
pub fn focus(self: *Self, _view: ?*View) void {
|
pub fn focus(self: *Self, _target: ?*View) void {
|
||||||
var view = _view;
|
var target = _target;
|
||||||
|
|
||||||
// While a layer surface is focused, views may not recieve focus
|
// While a layer surface is focused, views may not recieve focus
|
||||||
if (self.focused == .layer) return;
|
if (self.focused == .layer) return;
|
||||||
|
|
||||||
// If the view is not currently visible, behave as if null was passed
|
// If the view is not currently visible, behave as if null was passed
|
||||||
if (view) |v| {
|
if (target) |view| {
|
||||||
if (v.output != self.focused_output or
|
if (view.output != self.focused_output or
|
||||||
v.pending.tags & self.focused_output.pending.tags == 0) view = null;
|
view.pending.tags & self.focused_output.pending.tags == 0) target = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the target view is not fullscreen or null, then a fullscreen view
|
// If the target view is not fullscreen or null, then a fullscreen view
|
||||||
// will grab focus if visible.
|
// will grab focus if visible.
|
||||||
if (if (view) |v| !v.pending.fullscreen else true) {
|
if (if (target) |v| !v.pending.fullscreen else true) {
|
||||||
var it = ViewStack(*View).pendingIterator(self.focus_stack.first, self.focused_output.pending.tags);
|
const tags = self.focused_output.pending.tags;
|
||||||
view = while (it.next()) |node| {
|
var it = ViewStack(*View).iter(self.focus_stack.first, .forward, tags, pendingFilter);
|
||||||
if (node.view.output == self.focused_output and node.view.pending.fullscreen) break node.view;
|
target = while (it.next()) |view| {
|
||||||
} else 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
|
// 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);
|
const tags = self.focused_output.pending.tags;
|
||||||
view = while (it.next()) |node| {
|
var it = ViewStack(*View).iter(self.focus_stack.first, .forward, tags, pendingFilter);
|
||||||
if (node.view.output == self.focused_output) break node.view;
|
target = while (it.next()) |view| {
|
||||||
|
if (view.output == self.focused_output) break view;
|
||||||
} else null;
|
} else null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (view) |view_to_focus| {
|
if (target) |view| {
|
||||||
// Find or allocate a new node in the focus stack for the target view
|
// Find or allocate a new node in the focus stack for the target view
|
||||||
var it = self.focus_stack.first;
|
var it = self.focus_stack.first;
|
||||||
while (it) |node| : (it = node.next) {
|
while (it) |node| : (it = node.next) {
|
||||||
// If the view is found, move it to the top of the stack
|
// 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);
|
const new_focus_node = self.focus_stack.remove(node);
|
||||||
self.focus_stack.push(node);
|
self.focus_stack.push(node);
|
||||||
break;
|
break;
|
||||||
@ -153,18 +155,22 @@ pub fn focus(self: *Self, _view: ?*View) void {
|
|||||||
} else {
|
} else {
|
||||||
// The view is not in the stack, so allocate a new node and prepend it
|
// 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;
|
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);
|
self.focus_stack.push(new_focus_node);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Focus the target view
|
// Focus the target view
|
||||||
self.setFocusRaw(.{ .view = view_to_focus });
|
self.setFocusRaw(.{ .view = view });
|
||||||
} else {
|
} else {
|
||||||
// Otherwise clear the focus
|
// Otherwise clear the focus
|
||||||
self.setFocusRaw(.{ .none = {} });
|
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
|
/// Switch focus to the target, handling unfocus and input inhibition
|
||||||
/// properly. This should only be called directly if dealing with layers.
|
/// properly. This should only be called directly if dealing with layers.
|
||||||
pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void {
|
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.
|
// 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);
|
const focused_node = @fieldParentPtr(ViewStack(View).Node, "view", seat.focused.view);
|
||||||
var it = switch (direction) {
|
var it = switch (direction) {
|
||||||
.next => ViewStack(View).iterator(focused_node, output.current.tags),
|
.next => ViewStack(View).iter(focused_node, .forward, output.pending.tags, filter),
|
||||||
.previous => ViewStack(View).reverseIterator(focused_node, output.current.tags),
|
.previous => ViewStack(View).iter(focused_node, .reverse, output.pending.tags, filter),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Skip past the focused node
|
// Skip past the focused node
|
||||||
_ = it.next();
|
_ = it.next();
|
||||||
// Focus the next visible node if there is one
|
// Focus the next visible node if there is one
|
||||||
if (it.next()) |node| {
|
if (it.next()) |view| {
|
||||||
seat.focus(&node.view);
|
seat.focus(view);
|
||||||
output.root.startTransaction();
|
output.root.startTransaction();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -61,10 +61,14 @@ pub fn focusView(
|
|||||||
// 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 = switch (direction) {
|
var it = switch (direction) {
|
||||||
.next => ViewStack(View).iterator(output.views.first, output.current.tags),
|
.next => ViewStack(View).iter(output.views.first, .forward, output.pending.tags, filter),
|
||||||
.previous => ViewStack(View).reverseIterator(output.views.last, output.current.tags),
|
.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();
|
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
|
// If the first view that is part of the layout is focused, zoom
|
||||||
// the next view in the layout. Otherwise zoom the focused view.
|
// the next view in the layout. Otherwise zoom the focused view.
|
||||||
const output = seat.focused_output;
|
const output = seat.focused_output;
|
||||||
var it = ViewStack(View).iterator(output.views.first, output.current.tags);
|
var it = ViewStack(View).iter(output.views.first, .forward, output.pending.tags, filter);
|
||||||
const layout_first = while (it.next()) |node| {
|
const layout_first = @fieldParentPtr(ViewStack(View).Node, "view", it.next().?);
|
||||||
if (!node.view.pending.float and !node.view.pending.fullscreen) break node;
|
|
||||||
} else unreachable;
|
|
||||||
|
|
||||||
const focused_node = @fieldParentPtr(ViewStack(View).Node, "view", seat.focused.view);
|
const focused_node = @fieldParentPtr(ViewStack(View).Node, "view", seat.focused.view);
|
||||||
const zoom_node = if (focused_node == layout_first) blk: {
|
const zoom_node = if (focused_node == layout_first)
|
||||||
while (it.next()) |node| {
|
if (it.next()) |view| @fieldParentPtr(ViewStack(View).Node, "view", view) else null
|
||||||
if (!node.view.pending.float and !node.view.pending.fullscreen) break :blk node;
|
else
|
||||||
} else {
|
focused_node;
|
||||||
break :blk null;
|
|
||||||
}
|
|
||||||
} else focused_node;
|
|
||||||
|
|
||||||
if (zoom_node) |to_bump| {
|
if (zoom_node) |to_bump| {
|
||||||
output.views.remove(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);
|
c.wlr_renderer_begin(wlr_renderer, width, height);
|
||||||
|
|
||||||
// Find the first visible fullscreen view in the stack if there is one
|
// Find the first visible fullscreen view in the stack if there is one
|
||||||
var it = ViewStack(View).iterator(output.views.first, output.current.tags);
|
var it = ViewStack(View).iter(output.views.first, .forward, output.current.tags, renderFilter);
|
||||||
const fullscreen_view = while (it.next()) |node| {
|
const fullscreen_view = while (it.next()) |view| {
|
||||||
if (node.view.current.fullscreen) break &node.view;
|
if (view.current.fullscreen) break view;
|
||||||
} else null;
|
} else null;
|
||||||
|
|
||||||
// If we have a fullscreen view to render, render it.
|
// 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);
|
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.
|
// The first view in the list is "on top" so iterate in reverse.
|
||||||
it = ViewStack(View).reverseIterator(output.views.last, output.current.tags);
|
it = ViewStack(View).iter(output.views.last, .reverse, output.current.tags, renderFilter);
|
||||||
while (it.next()) |node| {
|
while (it.next()) |view| {
|
||||||
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;
|
|
||||||
|
|
||||||
// Focused views are rendered on top of normal views, skip them for now
|
// Focused views are rendered on top of normal views, skip them for now
|
||||||
if (view.current.focus != 0) continue;
|
if (view.current.focus != 0) continue;
|
||||||
|
|
||||||
@ -92,14 +86,8 @@ pub fn renderOutput(output: *Output) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Render focused views
|
// Render focused views
|
||||||
it = ViewStack(View).reverseIterator(output.views.last, output.current.tags);
|
it = ViewStack(View).iter(output.views.last, .reverse, output.current.tags, renderFilter);
|
||||||
while (it.next()) |node| {
|
while (it.next()) |view| {
|
||||||
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;
|
|
||||||
|
|
||||||
// Skip unfocused views since we already rendered them
|
// Skip unfocused views since we already rendered them
|
||||||
if (view.current.focus == 0) continue;
|
if (view.current.focus == 0) continue;
|
||||||
|
|
||||||
@ -130,6 +118,14 @@ pub fn renderOutput(output: *Output) void {
|
|||||||
_ = c.wlr_output_commit(output.wlr_output);
|
_ = 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
|
/// Render all surfaces on the passed layer
|
||||||
fn renderLayer(output: Output, layer: std.TailQueue(LayerSurface), now: *c.timespec) void {
|
fn renderLayer(output: Output, layer: std.TailQueue(LayerSurface), now: *c.timespec) void {
|
||||||
var it = layer.first;
|
var it = layer.first;
|
||||||
|
@ -41,14 +41,8 @@ pub fn ViewStack(comptime T: type) type {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// Top/bottom nodes in the stack
|
/// Top/bottom nodes in the stack
|
||||||
first: ?*Node,
|
first: ?*Node = null,
|
||||||
last: ?*Node,
|
last: ?*Node = null,
|
||||||
|
|
||||||
/// Initialize an undefined stack
|
|
||||||
pub fn init(self: *Self) void {
|
|
||||||
self.first = null;
|
|
||||||
self.last = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a node to the top of the stack.
|
/// Add a node to the top of the stack.
|
||||||
pub fn push(self: *Self, new_node: *Node) void {
|
pub fn push(self: *Self, new_node: *Node) void {
|
||||||
@ -114,60 +108,42 @@ pub fn ViewStack(comptime T: type) type {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const Iterator = struct {
|
const Direction = enum {
|
||||||
|
forward,
|
||||||
|
reverse,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn Iter(comptime Context: type) type {
|
||||||
|
return struct {
|
||||||
it: ?*Node,
|
it: ?*Node,
|
||||||
tags: u32,
|
dir: Direction,
|
||||||
reverse: bool,
|
context: Context,
|
||||||
pending: bool,
|
filter: fn (*View, Context) bool,
|
||||||
|
|
||||||
/// Returns the next node in iteration order, or null if done.
|
/// Returns the next node in iteration order which passes the
|
||||||
/// This function is horribly ugly, but it's well tested below.
|
/// filter, or null if done.
|
||||||
pub fn next(self: *Iterator) ?*Node {
|
pub fn next(self: *@This()) ?*View {
|
||||||
while (self.it) |node| : (self.it = if (self.reverse) node.prev else node.next) {
|
return while (self.it) |node| : (self.it = if (self.dir == .forward) node.next else node.prev) {
|
||||||
if (if (self.pending)
|
const view = if (T == View) &node.view else node.view;
|
||||||
self.tags & node.view.pending.tags != 0
|
if (self.filter(view, self.context)) {
|
||||||
else
|
self.it = if (self.dir == .forward) node.next else node.prev;
|
||||||
self.tags & node.view.current.tags != 0) {
|
break view;
|
||||||
self.it = if (self.reverse) node.prev else node.next;
|
|
||||||
return node;
|
|
||||||
}
|
}
|
||||||
|
} else null;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// 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,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reverse iterator starting at the passed node and filtered by
|
/// Return a filtered iterator over the stack given a start node,
|
||||||
/// checking the passed tags against the current tags of each view.
|
/// iteration direction, and filter function. Views for which the
|
||||||
pub fn reverseIterator(start: ?*Node, tags: u32) Iterator {
|
/// filter function returns false will be skipped.
|
||||||
return Iterator{
|
pub fn iter(
|
||||||
.it = start,
|
start: ?*Node,
|
||||||
.tags = tags,
|
dir: Direction,
|
||||||
.reverse = true,
|
context: var,
|
||||||
.pending = false,
|
filter: fn (*View, @TypeOf(context)) bool,
|
||||||
};
|
) Iter(@TypeOf(context)) {
|
||||||
}
|
return .{ .it = start, .dir = dir, .context = context, .filter = filter };
|
||||||
|
|
||||||
/// 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,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -177,8 +153,7 @@ test "push/remove (*View)" {
|
|||||||
|
|
||||||
const allocator = testing.allocator;
|
const allocator = testing.allocator;
|
||||||
|
|
||||||
var views: ViewStack(*View) = undefined;
|
var views = ViewStack(*View){};
|
||||||
views.init();
|
|
||||||
|
|
||||||
const one = try allocator.create(ViewStack(*View).Node);
|
const one = try allocator.create(ViewStack(*View).Node);
|
||||||
defer allocator.destroy(one);
|
defer allocator.destroy(one);
|
||||||
@ -309,8 +284,21 @@ test "iteration (View)" {
|
|||||||
|
|
||||||
const allocator = testing.allocator;
|
const allocator = testing.allocator;
|
||||||
|
|
||||||
var views: ViewStack(View) = undefined;
|
const filters = struct {
|
||||||
views.init();
|
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);
|
const one_a_pb = try allocator.create(ViewStack(View).Node);
|
||||||
defer allocator.destroy(one_a_pb);
|
defer allocator.destroy(one_a_pb);
|
||||||
@ -343,94 +331,71 @@ test "iteration (View)" {
|
|||||||
views.push(five_b); // {5, 4, 1, 3}
|
views.push(five_b); // {5, 4, 1, 3}
|
||||||
views.push(two_a); // {2, 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));
|
var it = ViewStack(View).iter(views.first, .forward, {}, filters.all);
|
||||||
testing.expect((if (it.next()) |node| &node.view else null) == &two_a.view);
|
testing.expect(it.next() == &two_a.view);
|
||||||
testing.expect((if (it.next()) |node| &node.view else null) == &five_b.view);
|
testing.expect(it.next() == &five_b.view);
|
||||||
testing.expect((if (it.next()) |node| &node.view else null) == &four_b.view);
|
testing.expect(it.next() == &four_b.view);
|
||||||
testing.expect((if (it.next()) |node| &node.view else null) == &one_a_pb.view);
|
testing.expect(it.next() == &one_a_pb.view);
|
||||||
testing.expect((if (it.next()) |node| &node.view else null) == &three_b_pa.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);
|
testing.expect(it.next() == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iteration over 'a' tags
|
// Iteration over 'a' tags
|
||||||
{
|
{
|
||||||
var it = ViewStack(View).iterator(views.first, 1 << 0);
|
var it = ViewStack(View).iter(views.first, .forward, @as(u32, 1 << 0), filters.current);
|
||||||
testing.expect((if (it.next()) |node| &node.view else null) == &two_a.view);
|
testing.expect(it.next() == &two_a.view);
|
||||||
testing.expect((if (it.next()) |node| &node.view else null) == &one_a_pb.view);
|
testing.expect(it.next() == &one_a_pb.view);
|
||||||
testing.expect(it.next() == null);
|
testing.expect(it.next() == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iteration over 'b' tags
|
// Iteration over 'b' tags
|
||||||
{
|
{
|
||||||
var it = ViewStack(View).iterator(views.first, 1 << 1);
|
var it = ViewStack(View).iter(views.first, .forward, @as(u32, 1 << 1), filters.current);
|
||||||
testing.expect((if (it.next()) |node| &node.view else null) == &five_b.view);
|
testing.expect(it.next() == &five_b.view);
|
||||||
testing.expect((if (it.next()) |node| &node.view else null) == &four_b.view);
|
testing.expect(it.next() == &four_b.view);
|
||||||
testing.expect((if (it.next()) |node| &node.view else null) == &three_b_pa.view);
|
testing.expect(it.next() == &three_b_pa.view);
|
||||||
testing.expect(it.next() == null);
|
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);
|
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));
|
var it = ViewStack(View).iter(views.last, .reverse, {}, filters.none);
|
||||||
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);
|
|
||||||
testing.expect(it.next() == null);
|
testing.expect(it.next() == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reverse iteration over 'a' tags
|
// Reverse iteration over 'a' tags
|
||||||
{
|
{
|
||||||
var it = ViewStack(View).reverseIterator(views.last, 1 << 0);
|
var it = ViewStack(View).iter(views.last, .reverse, @as(u32, 1 << 0), filters.current);
|
||||||
testing.expect((if (it.next()) |node| &node.view else null) == &one_a_pb.view);
|
testing.expect(it.next() == &one_a_pb.view);
|
||||||
testing.expect((if (it.next()) |node| &node.view else null) == &two_a.view);
|
testing.expect(it.next() == &two_a.view);
|
||||||
testing.expect(it.next() == null);
|
testing.expect(it.next() == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reverse iteration over 'b' tags
|
// Reverse iteration over 'b' tags
|
||||||
{
|
{
|
||||||
var it = ViewStack(View).reverseIterator(views.last, 1 << 1);
|
var it = ViewStack(View).iter(views.last, .reverse, @as(u32, 1 << 1), filters.current);
|
||||||
testing.expect((if (it.next()) |node| &node.view else null) == &three_b_pa.view);
|
testing.expect(it.next() == &three_b_pa.view);
|
||||||
testing.expect((if (it.next()) |node| &node.view else null) == &four_b.view);
|
testing.expect(it.next() == &four_b.view);
|
||||||
testing.expect((if (it.next()) |node| &node.view else null) == &five_b.view);
|
testing.expect(it.next() == &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);
|
|
||||||
testing.expect(it.next() == null);
|
testing.expect(it.next() == null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user