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:
parent
89d0fb012d
commit
c04112b81a
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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{
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.height = 0,
|
||||
.width = 0,
|
||||
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", .{});
|
||||
switch (self.impl) {
|
||||
.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 {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
);
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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())
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
else
|
||||
self.tags & node.view.current_tags != 0
|
||||
self.tags & node.view.pending.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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user