selections!
This commit is contained in:
parent
b93dcfb797
commit
a9a64d2f18
|
@ -70,6 +70,7 @@ pub fn build(b: *std.build.Builder) void {
|
||||||
const exe_tests = b.addTest("main.zig");
|
const exe_tests = b.addTest("main.zig");
|
||||||
exe_tests.setTarget(target);
|
exe_tests.setTarget(target);
|
||||||
exe_tests.setBuildMode(mode);
|
exe_tests.setBuildMode(mode);
|
||||||
|
exe_tests.linkLibC();
|
||||||
|
|
||||||
const test_step = b.step("test", "Run unit tests");
|
const test_step = b.step("test", "Run unit tests");
|
||||||
test_step.dependOn(&exe_tests.step);
|
test_step.dependOn(&exe_tests.step);
|
||||||
|
|
307
main.zig
307
main.zig
|
@ -16,8 +16,17 @@ const Cairo = @cImport({
|
||||||
});
|
});
|
||||||
|
|
||||||
const Point = struct {
|
const Point = struct {
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
x: i32 = 0,
|
x: i32 = 0,
|
||||||
y: i32 = 0,
|
y: i32 = 0,
|
||||||
|
|
||||||
|
fn inverted(self: *const Self) Self {
|
||||||
|
return Self{
|
||||||
|
.x = self.x * -1,
|
||||||
|
.y = self.y * -1,
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const Size = struct {
|
const Size = struct {
|
||||||
|
@ -58,6 +67,7 @@ const Buffer = struct {
|
||||||
} = .{},
|
} = .{},
|
||||||
size: Size = .{},
|
size: Size = .{},
|
||||||
data: []u8 = undefined,
|
data: []u8 = undefined,
|
||||||
|
in_use: std.atomic.Atomic(bool) = std.atomic.Atomic(bool).init(false),
|
||||||
|
|
||||||
fn randomName() [13]u8 {
|
fn randomName() [13]u8 {
|
||||||
var name = [_]u8{'/', 'z', 'l', 'u','r','p','-','0','0','0','0','0','0'};
|
var name = [_]u8{'/', 'z', 'l', 'u','r','p','-','0','0','0','0','0','0'};
|
||||||
|
@ -118,6 +128,8 @@ const Buffer = struct {
|
||||||
.ctx = cairo_ctx,
|
.ctx = cairo_ctx,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
self.buffer.setListener(*Self, bufferListener, self);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deinit(self: *Self) void {
|
fn deinit(self: *Self) void {
|
||||||
|
@ -127,8 +139,70 @@ const Buffer = struct {
|
||||||
Cairo.cairo_destroy(self.cairo.ctx);
|
Cairo.cairo_destroy(self.cairo.ctx);
|
||||||
Cairo.cairo_surface_destroy(self.cairo.surface);
|
Cairo.cairo_surface_destroy(self.cairo.surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn bufferListener(buffer: *wl.Buffer, event: wl.Buffer.Event, self: *Self) void {
|
||||||
|
_ = buffer;
|
||||||
|
|
||||||
|
switch (event) {
|
||||||
|
.release => {
|
||||||
|
self.in_use.store(false, .Monotonic);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Buffers = struct {
|
||||||
|
const Self = @This();
|
||||||
|
state: *State.Init,
|
||||||
|
buffers: [2]?Buffer = undefined,
|
||||||
|
|
||||||
|
fn init(self: *Self, state: *State.Init) !void {
|
||||||
|
self.* = Self{
|
||||||
|
.state = state,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (self.buffers) |*buffer| {
|
||||||
|
buffer.* = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns any already initialized and not currently used buffer
|
||||||
|
fn getAvailableBuffer(self: *Self, size: Size) ?*Buffer {
|
||||||
|
for (self.buffers) |*buffer| {
|
||||||
|
if (buffer.*) |*buf| {
|
||||||
|
if (!buf.in_use.load(.Monotonic)) {
|
||||||
|
if (buf.size.width != size.width or buf.size.height != size.height) {
|
||||||
|
buf.deinit();
|
||||||
|
buf.* = .{};
|
||||||
|
buf.init(self.state.shm, size) catch {
|
||||||
|
buffer.* = null;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (self.buffers) |*buffer| {
|
||||||
|
if (buffer.* == null) {
|
||||||
|
buffer.* = .{};
|
||||||
|
buffer.*.?.init(self.state.shm, size) catch {
|
||||||
|
buffer.* = null;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
return &buffer.*.?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test "buffers" {
|
||||||
|
std.testing.refAllDecls(Buffers);
|
||||||
|
}
|
||||||
|
|
||||||
const Box = struct {
|
const Box = struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
|
@ -183,6 +257,48 @@ const Box = struct {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn getInner(comptime T: anytype, v: anytype) T {
|
||||||
|
switch (@typeInfo(T)) {
|
||||||
|
.Float => {
|
||||||
|
return @intToFloat(T, v);
|
||||||
|
},
|
||||||
|
.Int => {
|
||||||
|
return @intCast(T, v);
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
@compileError("can only cast positional scalar to integer or floating point types");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getX(self: *const Self, comptime T: anytype) T {
|
||||||
|
return getInner(T, self.position.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getY(self: *const Self, comptime T: anytype) T {
|
||||||
|
return getInner(T, self.position.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getWidth(self: *const Self, comptime T: anytype) T {
|
||||||
|
return getInner(T, self.extents.width);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getHeight(self: *const Self, comptime T: anytype) T {
|
||||||
|
return getInner(T, self.extents.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn translate(self: *Self, delta: Point) void {
|
||||||
|
self.position.x += delta.x;
|
||||||
|
self.position.y += delta.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn translated(self: Self, delta: Point) Self {
|
||||||
|
var new = self;
|
||||||
|
new.translate(delta);
|
||||||
|
|
||||||
|
return new;
|
||||||
|
}
|
||||||
|
|
||||||
fn contains(self: *const Self, point: Point) bool {
|
fn contains(self: *const Self, point: Point) bool {
|
||||||
const x1 = self.position.x;
|
const x1 = self.position.x;
|
||||||
const y1 = self.position.y;
|
const y1 = self.position.y;
|
||||||
|
@ -352,10 +468,16 @@ const Output = struct {
|
||||||
size: Size = Size{},
|
size: Size = Size{},
|
||||||
|
|
||||||
configured: bool = false,
|
configured: bool = false,
|
||||||
|
needs_redraw: bool = false,
|
||||||
|
|
||||||
|
buffers: Buffers = undefined,
|
||||||
|
frame_callback: ?*wl.Callback = null,
|
||||||
|
|
||||||
fn init(self: *Self, state: *State.Init) !void {
|
fn init(self: *Self, state: *State.Init) !void {
|
||||||
std.debug.print("output.init()\n", .{});
|
std.debug.print("output.init()\n", .{});
|
||||||
|
|
||||||
self.state = state;
|
self.state = state;
|
||||||
|
try self.buffers.init(state);
|
||||||
|
|
||||||
self.output.setListener(*Self, outputListener, self);
|
self.output.setListener(*Self, outputListener, self);
|
||||||
|
|
||||||
|
@ -382,8 +504,126 @@ const Output = struct {
|
||||||
self.output.destroy();
|
self.output.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn setSourceU32(c: *Cairo.cairo_t, color: u32) void {
|
||||||
|
Cairo.cairo_set_source_rgba(
|
||||||
|
c,
|
||||||
|
@intToFloat(f32, color >> (3 * 8) & 0xff) / 255.0,
|
||||||
|
@intToFloat(f32, color >> (2 * 8) & 0xff) / 255.0,
|
||||||
|
@intToFloat(f32, color >> (1 * 8) & 0xff) / 255.0,
|
||||||
|
@intToFloat(f32, color >> (0 * 8) & 0xff) / 255.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drawRect(buffer: *Buffer, box: Box, color: u32) void {
|
||||||
|
const cairo = buffer.cairo.ctx;
|
||||||
|
setSourceU32(cairo, color);
|
||||||
|
Cairo.cairo_rectangle(
|
||||||
|
cairo,
|
||||||
|
box.getX(f64),
|
||||||
|
box.getY(f64),
|
||||||
|
box.getWidth(f64),
|
||||||
|
box.getHeight(f64),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(self: *Self, buffer: *Buffer) void {
|
||||||
|
const cairo = &buffer.cairo;
|
||||||
|
Cairo.cairo_set_operator(cairo.ctx, Cairo.CAIRO_OPERATOR_SOURCE);
|
||||||
|
setSourceU32(cairo.ctx, 0xFFFFFF40);
|
||||||
|
Cairo.cairo_paint(cairo.ctx);
|
||||||
|
|
||||||
|
var seat_iter = self.state.seats.iterator(.forward);
|
||||||
|
while (seat_iter.next()) |link| {
|
||||||
|
const seat = link.get();
|
||||||
|
if (seat.pointer == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seat.pointer.?.selection.getSelectionBox()) |box| {
|
||||||
|
// draw inner box
|
||||||
|
const corrected_box = box.translated(self.logical_geometry.position.inverted());
|
||||||
|
std.debug.print("selection: {}\n", .{corrected_box});
|
||||||
|
|
||||||
|
drawRect(buffer, corrected_box, 0x0);
|
||||||
|
Cairo.cairo_fill(cairo.ctx);
|
||||||
|
|
||||||
|
Cairo.cairo_set_line_width(cairo.ctx, 2.0);
|
||||||
|
drawRect(buffer, corrected_box, 0x000000FF);
|
||||||
|
Cairo.cairo_stroke(cairo.ctx);
|
||||||
|
|
||||||
|
// TODO: draw dimensions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn commitFrame(self: *Self) void {
|
fn commitFrame(self: *Self) void {
|
||||||
_ = self;
|
const size = Size{
|
||||||
|
.width = self.size.width * @intCast(u32, self.scale),
|
||||||
|
.height = self.size.height * @intCast(u32, self.scale),
|
||||||
|
};
|
||||||
|
|
||||||
|
// FIXME: maybe somehow make this retry to get another buffer if the one
|
||||||
|
// that was returned has just become in-use?
|
||||||
|
if (self.buffers.getAvailableBuffer(size)) |buffer| {
|
||||||
|
if (buffer.in_use.compareAndSwap(false, true, .Release, .Monotonic) == null) {
|
||||||
|
Cairo.cairo_identity_matrix(buffer.cairo.ctx);
|
||||||
|
Cairo.cairo_scale(
|
||||||
|
buffer.cairo.ctx,
|
||||||
|
@intToFloat(f64, self.scale),
|
||||||
|
@intToFloat(f64, self.scale),
|
||||||
|
);
|
||||||
|
|
||||||
|
// RENDER
|
||||||
|
self.render(buffer);
|
||||||
|
|
||||||
|
if (self.frame_callback == null) {
|
||||||
|
self.frame_callback = self.surface.frame() catch null;
|
||||||
|
if (self.frame_callback) |frame| {
|
||||||
|
frame.setListener(*Self, frameCallbackListener, self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.surface.attach(buffer.buffer, 0, 0);
|
||||||
|
self.surface.damage(0, 0,
|
||||||
|
@intCast(i32, self.size.width),
|
||||||
|
@intCast(i32, self.size.height),
|
||||||
|
);
|
||||||
|
self.surface.setBufferScale(self.scale);
|
||||||
|
self.surface.commit();
|
||||||
|
|
||||||
|
self.needs_redraw = false;
|
||||||
|
} else {
|
||||||
|
std.debug.print("failed to CAS in_use.\n", .{});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std.debug.print("No buffer available!\n", .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn requestRedraw(self: *Self) !void {
|
||||||
|
self.needs_redraw = true;
|
||||||
|
|
||||||
|
if (self.frame_callback == null) {
|
||||||
|
self.frame_callback = try self.surface.frame();
|
||||||
|
self.frame_callback.?.setListener(*Self, frameCallbackListener, self);
|
||||||
|
self.surface.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frameCallbackListener(frame: *wl.Callback, event: wl.Callback.Event, self: *Self) void {
|
||||||
|
switch (event) {
|
||||||
|
.done => {
|
||||||
|
frame.destroy();
|
||||||
|
self.frame_callback = null;
|
||||||
|
|
||||||
|
if (self.needs_redraw) {
|
||||||
|
self.commitFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
std.debug.print("frame_callback done\n", .{});
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn xdgOutputListener(output: *zxdg.OutputV1, event: zxdg.OutputV1.Event, self: *Self) void {
|
fn xdgOutputListener(output: *zxdg.OutputV1, event: zxdg.OutputV1.Event, self: *Self) void {
|
||||||
|
@ -494,6 +734,18 @@ const Seat = struct {
|
||||||
fn None() SelectionState {
|
fn None() SelectionState {
|
||||||
return SelectionState.none;
|
return SelectionState.none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn getSelectionBox(self: SelectionState) ?Box {
|
||||||
|
switch (self) {
|
||||||
|
.updating => |selection| {
|
||||||
|
return selection.asBox();
|
||||||
|
},
|
||||||
|
.post => |box| {
|
||||||
|
return box;
|
||||||
|
},
|
||||||
|
else => {return null;},
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fn init(self: *Self, state: *State.Init) void {
|
fn init(self: *Self, state: *State.Init) void {
|
||||||
|
@ -503,11 +755,23 @@ const Seat = struct {
|
||||||
self.seat.setListener(*Self, seatListener, self);
|
self.seat.setListener(*Self, seatListener, self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn calculateRedraws(self: *Self) void {
|
||||||
|
if (self.pointer) |ptr| {
|
||||||
|
const box = ptr.selection.getSelectionBox();
|
||||||
|
|
||||||
|
var output_iter = self.state.outputs.iterator(.forward);
|
||||||
|
while (output_iter.next()) |link| {
|
||||||
|
const output = link.get();
|
||||||
|
if (box == null or output.logical_geometry.intersects(&box.?)) {
|
||||||
|
output.requestRedraw() catch {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn pointerListener(pointer: *wl.Pointer, event: wl.Pointer.Event, self: *Self) void {
|
fn pointerListener(pointer: *wl.Pointer, event: wl.Pointer.Event, self: *Self) void {
|
||||||
_ = pointer;
|
_ = pointer;
|
||||||
|
|
||||||
std.debug.print("selection: {}\n", .{self.pointer.?.selection});
|
|
||||||
|
|
||||||
switch (event) {
|
switch (event) {
|
||||||
.enter => {},
|
.enter => {},
|
||||||
.leave => {},
|
.leave => {},
|
||||||
|
@ -520,9 +784,11 @@ const Seat = struct {
|
||||||
switch (self.pointer.?.selection) {
|
switch (self.pointer.?.selection) {
|
||||||
.pre => {
|
.pre => {
|
||||||
self.pointer.?.selection = .{.updating = .{.start = end, .end = end}};
|
self.pointer.?.selection = .{.updating = .{.start = end, .end = end}};
|
||||||
|
return self.calculateRedraws();
|
||||||
},
|
},
|
||||||
.updating => |*selection| {
|
.updating => |*selection| {
|
||||||
selection.end = end;
|
selection.end = end;
|
||||||
|
return self.calculateRedraws();
|
||||||
},
|
},
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
|
@ -537,6 +803,7 @@ const Seat = struct {
|
||||||
.updating => |selection| {
|
.updating => |selection| {
|
||||||
const box = selection.asBox();
|
const box = selection.asBox();
|
||||||
self.pointer.?.selection = .{.post = box};
|
self.pointer.?.selection = .{.post = box};
|
||||||
|
return self.calculateRedraws();
|
||||||
},
|
},
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
|
@ -614,7 +881,20 @@ const Seat = struct {
|
||||||
.pressed => {
|
.pressed => {
|
||||||
switch (keysym) {
|
switch (keysym) {
|
||||||
xkb.XKB_KEY_Escape, xkb.XKB_KEY_q => {
|
xkb.XKB_KEY_Escape, xkb.XKB_KEY_q => {
|
||||||
self.state.running = false;
|
|
||||||
|
if (self.pointer) |*ptr| {
|
||||||
|
switch (ptr.selection) {
|
||||||
|
.none => {
|
||||||
|
self.state.running = false;
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
ptr.selection = .none;
|
||||||
|
return self.calculateRedraws();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
|
@ -914,25 +1194,16 @@ const State = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stuff(self: *Self) !void {
|
fn stuff(self: *Self) !void {
|
||||||
var it = self.outputs.iterator(.forward);
|
|
||||||
if (it.next()) |link| {
|
|
||||||
const output = link.get();
|
|
||||||
|
|
||||||
var buffer: Buffer = undefined;
|
|
||||||
const width = output.size.width * @intCast(u32, output.scale);
|
|
||||||
const height = output.size.height * @intCast(u32, output.scale);
|
|
||||||
try buffer.init(self.shm, .{.width = width, .height = height});
|
|
||||||
std.mem.copy(u8, buffer.data, @embedFile("cat.bgra"));
|
|
||||||
|
|
||||||
output.surface.attach(buffer.buffer, 0, 0);
|
|
||||||
output.surface.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
while (self.running) {
|
while (self.running) {
|
||||||
if (self.dpy.dispatch() != .SUCCESS) return error.DispatchFailed;
|
if (self.dpy.dispatch() != .SUCCESS) return error.DispatchFailed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn finish(self: *Self, box: Box) void {
|
||||||
|
_ = self;
|
||||||
|
_ = box;
|
||||||
|
}
|
||||||
|
|
||||||
fn removeOutput(self: *Self, output: *Output) void {
|
fn removeOutput(self: *Self, output: *Output) void {
|
||||||
const link = ListItem(Output).fromValue(output);
|
const link = ListItem(Output).fromValue(output);
|
||||||
link.remove();
|
link.remove();
|
||||||
|
|
Loading…
Reference in a new issue