view: introduce state struct to simplify code

The state struct holds all of the state that is double-buffered and
applied through transactions. This more explicit handling simplifies
much of the code, and will allow for easier implementation of new
feature such as fullscreen.
This commit is contained in:
Isaac Freund 2020-06-27 22:43:15 +02:00
parent 89d0fb012d
commit c04112b81a
No known key found for this signature in database
GPG Key ID: 86DED400DDFD7A11
12 changed files with 105 additions and 124 deletions

View File

@ -181,7 +181,7 @@ fn layoutFull(self: *Self, visible_count: u32, output_tags: u32) void {
var it = ViewStack(View).pendingIterator(self.views.first, output_tags);
while (it.next()) |node| {
const view = &node.view;
if (view.mode == .layout) view.pending_box = full_box;
if (view.pending.mode == .layout) view.pending.box = full_box;
}
}
@ -286,8 +286,8 @@ fn layoutExternal(self: *Self, visible_count: u32, output_tags: u32) !void {
var view_it = ViewStack(View).pendingIterator(self.views.first, output_tags);
while (view_it.next()) |node| {
const view = &node.view;
if (view.mode == .layout) {
view.pending_box = view_boxen.items[i];
if (view.pending.mode == .layout) {
view.pending.box = view_boxen.items[i];
i += 1;
}
}
@ -310,7 +310,7 @@ pub fn arrangeViews(self: *Self) void {
var count: u32 = 0;
var it = ViewStack(View).pendingIterator(self.views.first, output_tags);
while (it.next()) |node| {
if (node.view.mode == .layout) count += 1;
if (node.view.pending.mode == .layout) count += 1;
}
break :blk count;
};

View File

@ -62,7 +62,7 @@ pub fn sendViewTags(self: Self) void {
var it = ViewStack(View).iterator(self.output.views.first, std.math.maxInt(u32));
while (it.next()) |node|
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);
log.crit(.river_status, "out of memory", .{});
return;

View File

@ -175,7 +175,7 @@ fn startTransaction(self: *Self) void {
fn handleTimeout(data: ?*c_void) callconv(.C) c_int {
const self = util.voidCast(Self, data.?);
log.err(.transaction, "time out occurred, some imperfect frames may be shown", .{});
log.err(.transaction, "timeout occurred, some imperfect frames may be shown", .{});
self.commitTransaction();
@ -225,19 +225,10 @@ fn commitTransaction(self: *Self) void {
var view_it = ViewStack(View).iterator(output.views.first, std.math.maxInt(u32));
while (view_it.next()) |view_node| {
const view = &view_node.view;
// Ensure that all pending state is cleared
// Apply pending state
view.pending_serial = null;
if (view.pending_box) |state| {
view.current_box = state;
view.pending_box = null;
}
// Apply possible pending tags
if (view.pending_tags) |tags| {
view.current_tags = tags;
view.pending_tags = null;
view_tags_changed = true;
}
if (view.pending.tags != view.current.tags) view_tags_changed = true;
view.current = view.pending;
view.dropSavedBuffers();
}
@ -247,7 +238,5 @@ fn commitTransaction(self: *Self) void {
// Iterate over all seats and update focus
var it = self.server.input_manager.seats.first;
while (it) |seat_node| : (it = seat_node.next) {
seat_node.data.focus(null);
}
while (it) |seat_node| : (it = seat_node.next) seat_node.data.focus(null);
}

View File

@ -125,7 +125,7 @@ pub fn focus(self: *Self, _view: ?*View) void {
// If view is null or not currently visible
if (if (view) |v|
v.output != self.focused_output or
v.current_tags & self.focused_output.current_focused_tags == 0
v.current.tags & self.focused_output.current_focused_tags == 0
else
true) {
// Set view to the first currently visible view on in the focus stack if any

View File

@ -41,6 +41,18 @@ const Mode = enum {
float,
};
const State = struct {
/// The output-relative coordinates and dimensions of the view. The
/// surface itself may have other dimensions which are stored in the
/// surface_box member.
box: Box,
/// The tags of the view, as a bitmask
tags: u32,
mode: Mode,
};
const SavedBuffer = struct {
wlr_buffer: *c.wlr_buffer,
box: Box,
@ -56,19 +68,15 @@ output: *Output,
/// This is non-null exactly when the view is mapped
wlr_surface: ?*c.wlr_surface,
/// The current mode of the view
mode: Mode,
/// True if the view is currently focused by at least one seat
focused: bool,
/// The current output-relative coordinates and dimensions of the view. The
/// surface itself may have other dimensions which are stored in the
/// surface_box member.
current_box: Box,
/// The double-buffered state of the view
current: State,
pending: State,
/// Pending dimensions of the view during a transaction
pending_box: ?Box,
/// The serial sent with the currently pending configure event
pending_serial: ?u32,
/// The currently commited geometry of the surface. The x/y may be negative if
/// for example the client has decided to draw CSD shadows a la GTK.
@ -85,29 +93,24 @@ saved_buffers: std.ArrayList(SavedBuffer),
natural_width: u32,
natural_height: u32,
current_tags: u32,
pending_tags: ?u32,
pending_serial: ?u32,
pub fn init(self: *Self, output: *Output, tags: u32, surface: var) void {
self.output = output;
self.wlr_surface = null;
self.mode = .layout;
self.focused = false;
self.current_box = Box{
self.current = .{
.box = .{
.x = 0,
.y = 0,
.height = 0,
.width = 0,
},
.tags = tags,
.mode = .layout,
};
self.pending_box = null;
self.current_tags = tags;
self.pending_tags = null;
self.pending = self.current;
self.pending_serial = null;
@ -135,13 +138,9 @@ pub fn needsConfigure(self: Self) bool {
}
pub fn configure(self: Self) void {
if (self.pending_box) |pending_box| {
switch (self.impl) {
.xdg_toplevel => |xdg_toplevel| xdg_toplevel.configure(pending_box),
.xwayland_view => |xwayland_view| xwayland_view.configure(pending_box),
}
} else {
log.err(.transaction, "configure called on a View with no pending box", .{});
.xdg_toplevel => |xdg_toplevel| xdg_toplevel.configure(self.pending.box),
.xwayland_view => |xwayland_view| xwayland_view.configure(self.pending.box),
}
}
@ -190,7 +189,8 @@ fn saveBuffersIterator(
}
}
/// Set the focued bool and the active state of the view if it is a toplevel
/// Set the focused bool and the active state of the view if it is a toplevel
/// TODO: This is insufficient for multi-seat, probably need a focus counter.
pub fn setFocused(self: *Self, focused: bool) void {
self.focused = focused;
switch (self.impl) {
@ -199,30 +199,6 @@ pub fn setFocused(self: *Self, focused: bool) void {
}
}
/// Set the mode of the view to the given mode
pub fn setMode(self: *Self, mode: Mode) void {
switch (self.mode) {
.layout => switch (mode) {
.layout => {},
.float => {
self.mode = .float;
self.pending_box = Box{
.x = std.math.max(0, @divTrunc(@intCast(i32, self.output.usable_box.width) -
@intCast(i32, self.natural_width), 2)),
.y = std.math.max(0, @divTrunc(@intCast(i32, self.output.usable_box.height) -
@intCast(i32, self.natural_height), 2)),
.width = self.natural_width,
.height = self.natural_height,
};
},
},
.float => switch (mode) {
.float => {},
.layout => self.mode = .layout,
},
}
}
/// Move a view from one output to another, sending the required enter/leave
/// events.
pub fn sendToOutput(self: *Self, destination_output: *Output) void {

View File

@ -62,8 +62,6 @@ pub fn init(self: *Self, view: *View, wlr_xdg_surface: *c.wlr_xdg_surface) void
/// Returns true if a configure must be sent to ensure the dimensions of the
/// pending_box are applied.
pub fn needsConfigure(self: Self) bool {
const pending_box = self.view.pending_box orelse return false;
const wlr_xdg_toplevel: *c.wlr_xdg_toplevel = @field(
self.wlr_xdg_surface,
c.wlr_xdg_surface_union,
@ -73,8 +71,8 @@ pub fn needsConfigure(self: Self) bool {
// sync with the current dimensions or be the dimensions sent with the
// most recent configure. In both cases server_pending has the values we
// want to check against.
return pending_box.width != wlr_xdg_toplevel.server_pending.width or
pending_box.height != wlr_xdg_toplevel.server_pending.height;
return self.view.pending.box.width != wlr_xdg_toplevel.server_pending.width or
self.view.pending.box.height != wlr_xdg_toplevel.server_pending.height;
}
/// Send a configure event, applying the width/height of the pending box.
@ -109,8 +107,8 @@ pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*c.wlr_surfa
const view = self.view;
return c.wlr_xdg_surface_surface_at(
self.wlr_xdg_surface,
ox - @intToFloat(f64, view.current_box.x - view.surface_box.x),
oy - @intToFloat(f64, view.current_box.y - view.surface_box.y),
ox - @intToFloat(f64, view.current.box.x - view.surface_box.x),
oy - @intToFloat(f64, view.current.box.y - view.surface_box.y),
sx,
sy,
);
@ -171,7 +169,15 @@ fn handleMap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
for (root.server.config.float_filter.items) |filter_app_id| {
// Make views with app_ids listed in the float filter float
if (std.mem.eql(u8, std.mem.span(app_id), std.mem.span(filter_app_id))) {
view.setMode(.float);
view.pending.mode = .float;
view.pending.box = .{
.x = std.math.max(0, @divTrunc(@intCast(i32, view.output.usable_box.width) -
@intCast(i32, view.natural_width), 2)),
.y = std.math.max(0, @divTrunc(@intCast(i32, view.output.usable_box.height) -
@intCast(i32, view.natural_height), 2)),
.width = view.natural_width,
.height = view.natural_height,
};
break;
}
} else if ((wlr_xdg_toplevel.parent != null) or
@ -179,7 +185,15 @@ fn handleMap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
(state.min_width == state.max_width or state.min_height == state.max_height)))
{
// If the toplevel has a parent or is of fixed size make it float
view.setMode(.float);
view.pending.mode = .float;
view.pending.box = .{
.x = std.math.max(0, @divTrunc(@intCast(i32, view.output.usable_box.width) -
@intCast(i32, view.natural_width), 2)),
.y = std.math.max(0, @divTrunc(@intCast(i32, view.output.usable_box.height) -
@intCast(i32, view.natural_height), 2)),
.width = view.natural_width,
.height = view.natural_height,
};
}
// If the toplevel has no parent, inform it that it is tiled. This
@ -246,5 +260,5 @@ fn handleNewPopup(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
// This will free itself on destroy
var xdg_popup = util.gpa.create(XdgPopup) catch unreachable;
xdg_popup.init(self.view.output, &self.view.current_box, wlr_xdg_popup);
xdg_popup.init(self.view.output, &self.view.current.box, wlr_xdg_popup);
}

View File

@ -57,11 +57,8 @@ pub fn init(self: *Self, view: *View, wlr_xwayland_surface: *c.wlr_xwayland_surf
}
pub fn needsConfigure(self: Self) bool {
const view = self.view;
if (view.pending_box) |pending_box|
return view.current_box.width != pending_box.width or
view.current_box.height != pending_box.height;
return false;
return self.view.current.box.width != self.view.pending.box.width or
self.view.current.box.height != self.view.pending.box.height;
}
/// Tell the client to take a new size
@ -105,8 +102,8 @@ pub fn forEachSurface(
pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*c.wlr_surface {
return c.wlr_surface_surface_at(
self.wlr_xwayland_surface.surface,
ox - @intToFloat(f64, self.view.current_box.x),
oy - @intToFloat(f64, self.view.current_box.y),
ox - @intToFloat(f64, self.view.current.box.x),
oy - @intToFloat(f64, self.view.current.box.y),
sx,
sy,
);

View File

@ -43,10 +43,8 @@ pub fn setViewTags(
) Error!void {
const tags = try parseTags(allocator, args, out);
if (seat.focused_view) |view| {
if (view.current_tags != tags) {
view.pending_tags = tags;
seat.input_manager.server.root.arrange();
}
view.pending.tags = tags;
view.output.root.arrange();
}
}
@ -75,10 +73,10 @@ pub fn toggleViewTags(
) Error!void {
const tags = try parseTags(allocator, args, out);
if (seat.focused_view) |view| {
const new_tags = view.current_tags ^ tags;
const new_tags = view.current.tags ^ tags;
if (new_tags != 0) {
view.pending_tags = new_tags;
seat.input_manager.server.root.arrange();
view.pending.tags = new_tags;
view.output.root.arrange();
}
}
}

View File

@ -30,9 +30,19 @@ pub fn toggleFloat(
) Error!void {
if (args.len > 1) return Error.TooManyArguments;
if (seat.focused_view) |view| {
switch (view.mode) {
.layout => view.setMode(.float),
.float => view.setMode(.layout),
switch (view.current.mode) {
.layout => {
view.pending.mode = .float;
view.pending.box = .{
.x = std.math.max(0, @divTrunc(@intCast(i32, view.output.usable_box.width) -
@intCast(i32, view.natural_width), 2)),
.y = std.math.max(0, @divTrunc(@intCast(i32, view.output.usable_box.height) -
@intCast(i32, view.natural_height), 2)),
.width = view.natural_width,
.height = view.natural_height,
};
},
.float => view.pending.mode = .layout,
}
view.output.root.arrange();
}

View File

@ -37,7 +37,7 @@ pub fn zoom(
const focused_node = @fieldParentPtr(ViewStack(View).Node, "view", current_focus);
// Only zoom views that are part of the layout
if (current_focus.mode != .layout) return;
if (current_focus.current.mode != .layout) return;
var it = ViewStack(View).iterator(output.views.first, output.current_focused_tags);
const zoom_node = if (focused_node == it.next())

View File

@ -68,7 +68,7 @@ pub fn renderOutput(output: *Output) void {
// 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;
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
if (view.focused) continue;
@ -84,7 +84,7 @@ pub fn renderOutput(output: *Output) void {
// 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;
if (view.current.box.width == 0 or view.current.box.height == 0) continue;
// Skip unfocused views since we already rendered them
if (!view.focused) continue;
@ -144,8 +144,8 @@ fn renderView(output: Output, view: *View, now: *c.timespec) void {
output,
saved_buffer.wlr_buffer.texture,
.{
.x = saved_buffer.box.x + view.current_box.x - view.saved_surface_box.x,
.y = saved_buffer.box.y + view.current_box.y - view.saved_surface_box.y,
.x = saved_buffer.box.x + view.current.box.x - view.saved_surface_box.x,
.y = saved_buffer.box.y + view.current.box.y - view.saved_surface_box.y,
.width = @intCast(c_int, saved_buffer.box.width),
.height = @intCast(c_int, saved_buffer.box.height),
},
@ -156,8 +156,8 @@ fn renderView(output: Output, view: *View, now: *c.timespec) void {
// a transaction and may simply render each toplevel surface.
var rdata = SurfaceRenderData{
.output = &output,
.output_x = view.current_box.x - view.surface_box.x,
.output_y = view.current_box.y - view.surface_box.y,
.output_x = view.current.box.x - view.surface_box.x,
.output_y = view.current.box.y - view.surface_box.y,
.when = now,
};
@ -248,29 +248,29 @@ fn renderBorders(output: Output, view: *View, now: *c.timespec) void {
const border_width = output.root.server.config.border_width;
// left and right, covering the corners as well
border.y = view.current_box.y - @intCast(i32, border_width);
border.y = view.current.box.y - @intCast(i32, border_width);
border.width = border_width;
border.height = view.current_box.height + border_width * 2;
border.height = view.current.box.height + border_width * 2;
// left
border.x = view.current_box.x - @intCast(i32, border_width);
border.x = view.current.box.x - @intCast(i32, border_width);
renderRect(output, border, color);
// right
border.x = view.current_box.x + @intCast(i32, view.current_box.width);
border.x = view.current.box.x + @intCast(i32, view.current.box.width);
renderRect(output, border, color);
// top and bottom
border.x = view.current_box.x;
border.width = view.current_box.width;
border.x = view.current.box.x;
border.width = view.current.box.width;
border.height = border_width;
// top
border.y = view.current_box.y - @intCast(i32, border_width);
border.y = view.current.box.y - @intCast(i32, border_width);
renderRect(output, border, color);
// bottom border
border.y = view.current_box.y + @intCast(i32, view.current_box.height);
border.y = view.current.box.y + @intCast(i32, view.current.box.height);
renderRect(output, border, color);
}

View File

@ -93,12 +93,9 @@ pub fn ViewStack(comptime T: type) type {
pub fn next(self: *Iterator) ?*Node {
while (self.it) |node| : (self.it = if (self.reverse) node.prev else node.next) {
if (if (self.pending)
if (node.view.pending_tags) |pending_tags|
self.tags & pending_tags != 0
self.tags & node.view.pending.tags != 0
else
self.tags & node.view.current_tags != 0
else
self.tags & node.view.current_tags != 0) {
self.tags & node.view.current.tags != 0) {
self.it = if (self.reverse) node.prev else node.next;
return node;
}