Keyboard and rotation!

This commit is contained in:
2026-03-07 14:29:17 +01:00
parent 001015aaf8
commit 1a647eced2
5 changed files with 245 additions and 53 deletions
+3
View File
@@ -8,6 +8,7 @@ pub fn build(b: *std.Build) void {
// of this build script using `b.option()`. All defined flags (including
// target and optimize options) will be listed when running `zig build --help`
// in this directory.
const llvm = b.option(bool, "llvm", "Build with llvm");
const mod = b.addModule("puzzle", .{
.root_source_file = b.path("src/root.zig"),
@@ -16,6 +17,7 @@ pub fn build(b: *std.Build) void {
const exe = b.addExecutable(.{
.name = "wl-main",
.use_llvm = llvm,
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
@@ -27,6 +29,7 @@ pub fn build(b: *std.Build) void {
});
exe.root_module.link_libc = true;
exe.root_module.linkSystemLibrary("wayland-client", .{});
exe.root_module.linkSystemLibrary("xkbcommon", .{});
exe.root_module.addIncludePath(b.path("src"));
exe.root_module.addCSourceFiles(.{
+3
View File
@@ -4,9 +4,12 @@ mkShell {
zig
zls
wayland-scanner
gdb
lldb
];
buildInputs = [
wayland
libxkbcommon
];
}
+128 -6
View File
@@ -1,4 +1,5 @@
const std = @import("std");
const linux = std.os.linux;
const puzzle = @import("puzzle");
@@ -7,6 +8,7 @@ const c = @cImport({
@cInclude("wayland-client-core.h");
@cInclude("xdg-shell.h");
@cInclude("linux/input-event-codes.h");
@cInclude("xkbcommon/xkbcommon.h");
});
const WlPointerEventMask = packed struct(u16) {
@@ -45,6 +47,10 @@ const WaylandState = struct {
shared_memory: ?*c.wl_shm,
xdg_wm_base: ?*c.xdg_wm_base,
xkb_context: ?*c.xkb_context,
xkb_keymap: ?*c.xkb_keymap,
xkb_state: ?*c.xkb_state,
allocator: std.mem.Allocator,
resize: bool,
@@ -88,8 +94,8 @@ fn createSharedMemoryPool(wl_state: WaylandState) !MemoryPool {
const fd: c_int = try createShmFile(pool_size);
const prot = std.os.linux.PROT.READ | std.os.linux.PROT.WRITE;
const flags: std.os.linux.MAP = .{ .TYPE = .SHARED };
const prot = linux.PROT.READ | std.os.linux.PROT.WRITE;
const flags: linux.MAP = .{ .TYPE = .SHARED };
const ret = try std.posix.mmap(null, pool_size, prot, flags, fd, 0);
const pool = c.wl_shm_create_pool(wl_state.shared_memory, fd, @intCast(pool_size));
@@ -585,14 +591,124 @@ const wl_pointer_listener: c.struct_wl_pointer_listener = .{
.frame = wlPointerHandleFrame,
};
fn wlKeyboardHandleKeymap(
data: ?*anyopaque,
wl_keyboard: ?*c.struct_wl_keyboard,
format: u32,
fd: i32,
size: u32,
) callconv(.c) void {
_ = wl_keyboard;
std.debug.assert(format == c.WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1);
const wl_state: *WaylandState = @ptrCast(@alignCast(data));
const map_shm: []align(4096) const u8 = std.posix.mmap(null, size, linux.PROT.READ, .{ .TYPE = .SHARED }, fd, 0) catch {
std.debug.panic("[Wayland] Keyboard keymap handler failed mmap", .{});
};
const keymap: ?*c.struct_xkb_keymap = c.xkb_keymap_new_from_string(wl_state.xkb_context, @ptrCast(map_shm), c.XKB_KEYMAP_FORMAT_TEXT_V1, c.XKB_KEYMAP_COMPILE_NO_FLAGS);
std.posix.munmap(map_shm);
std.posix.close(fd);
const xkb_state: ?*c.struct_xkb_state = c.xkb_state_new(keymap);
// NOTE: Unmap existing ones
if (wl_state.xkb_keymap != null) c.xkb_keymap_unref(wl_state.xkb_keymap);
if (wl_state.xkb_state != null) c.xkb_state_unref(wl_state.xkb_state);
wl_state.xkb_keymap = keymap;
wl_state.xkb_state = xkb_state;
}
fn wlKeyboardHandleEnter(data: ?*anyopaque, keyboard: ?*c.wl_keyboard, serial: u32, surface: ?*c.wl_surface, keys: [*c]c.wl_array) callconv(.c) void {
_ = data;
_ = keyboard;
_ = serial;
_ = surface;
_ = keys;
}
fn wlKeyboardHandleLeave(data: ?*anyopaque, keyboard: ?*c.wl_keyboard, serial: u32, surface: ?*c.wl_surface) callconv(.c) void {
_ = data;
_ = keyboard;
_ = serial;
_ = surface;
}
fn wlKeyboardHandleKey(data: ?*anyopaque, keyboard: ?*c.wl_keyboard, serial: u32, time: u32, key: u32, state: u32) callconv(.c) void {
_ = keyboard;
_ = serial;
_ = time;
const wl_state: *WaylandState = @ptrCast(@alignCast(data));
var buf: [32:0]u8 = undefined;
const keycode: u32 = key + 8;
const sym: c.xkb_keysym_t = c.xkb_state_key_get_one_sym(wl_state.xkb_state, keycode);
_ = c.xkb_keysym_get_name(sym, &buf, buf.len);
const pressed = state == c.WL_KEYBOARD_KEY_STATE_PRESSED or state == c.WL_KEYBOARD_KEY_STATE_REPEATED;
std.debug.print("{s} {any}\n", .{buf, pressed});
switch (sym) {
c.XKB_KEY_Escape => {
wl_state.running = false;
},
c.XKB_KEY_space => {
wl_state.app_input.key.space.down = pressed;
},
c.XKB_KEY_r => {
wl_state.app_input.key.r.down = pressed;
},
c.XKB_KEY_q => {
wl_state.app_input.key.q.down = pressed;
},
else => {},
}
}
fn wlKeyboardHandleModifiers(data: ?*anyopaque, keyboard: ?*c.wl_keyboard, serial: u32, mods_depressed: u32, mods_latched: u32, mods_locked: u32, group: u32) callconv(.c) void {
_ = data;
_ = keyboard;
_ = serial;
_ = mods_depressed;
_ = mods_latched;
_ = mods_locked;
_ = group;
}
fn wlKeyboardHandleRepeatInfo(data: ?*anyopaque, keyboard: ?*c.wl_keyboard, rate: i32, delay: i32) callconv(.c) void {
_ = data;
_ = keyboard;
_ = rate;
_ = delay;
}
const wl_keyboard_listener: c.wl_keyboard_listener = .{
.keymap = wlKeyboardHandleKeymap,
.enter = wlKeyboardHandleEnter,
.leave = wlKeyboardHandleLeave,
.key = wlKeyboardHandleKey,
.modifiers = wlKeyboardHandleModifiers,
.repeat_info = wlKeyboardHandleRepeatInfo,
};
fn wlSeatHandleCapabilities(data: ?*anyopaque, wl_seat: ?*c.struct_wl_seat, capabilities: u32) callconv(.c) void {
const wl_state: *WaylandState = @ptrCast(@alignCast(data));
if (capabilities & c.WL_SEAT_CAPABILITY_POINTER == 1) {
if (capabilities & c.WL_SEAT_CAPABILITY_POINTER == c.WL_SEAT_CAPABILITY_POINTER) {
std.debug.print("[Wayland] wl_seat: got pointer capability\n", .{});
const pointer: ?*c.struct_wl_pointer = c.wl_seat_get_pointer(wl_seat);
_ = c.wl_pointer_add_listener(pointer, &wl_pointer_listener, wl_state);
}
if (capabilities & c.WL_SEAT_CAPABILITY_KEYBOARD == c.WL_SEAT_CAPABILITY_KEYBOARD) {
std.debug.print("[Wayland] wl_seat: got keyboard capability\n", .{});
const keyboard = c.wl_seat_get_keyboard(wl_seat);
_ = c.wl_keyboard_add_listener(keyboard, &wl_keyboard_listener, wl_state);
}
}
fn wlSeatHandleName(data: ?*anyopaque, wl_seat: ?*c.struct_wl_seat, name: [*c]const u8) callconv(.c) void {
@@ -647,6 +763,9 @@ pub fn main() u8 {
std.debug.print("[Wayland] Connection established.\n", .{});
var wl_state: WaylandState = undefined;
wl_state.xkb_context = null;
wl_state.xkb_state = null;
wl_state.xkb_keymap = null;
const registry = c.wl_display_get_registry(display);
if (registry == null) {
@@ -656,6 +775,8 @@ pub fn main() u8 {
_ = c.wl_registry_add_listener(registry, &registry_listener, &wl_state);
wl_state.xkb_context = c.xkb_context_new(c.XKB_CONTEXT_NO_FLAGS);
_ = c.wl_display_roundtrip(display);
if (wl_state.compositor == null or wl_state.shared_memory == null or wl_state.seat == null) {
std.debug.print("ERROR: Wayland compositor, seat, or shm not bound.\n", .{});
@@ -835,13 +956,14 @@ pub fn main() u8 {
_ = c.wl_display_roundtrip(display);
std.posix.nanosleep(0, std.time.ns_per_ms * 32);
// std.posix.nanosleep(0, std.time.ns_per_ms * 32);
const current = timer.lap();
const current_ms = current / std.time.ns_per_ms;
const sleep_time_ms: f64 = @as(f64, @floatFromInt(sleep_time)) / @as(f64, @floatFromInt(std.time.ns_per_ms));
std.debug.print(
"Render: {d}ms, Sleeping: {d}ms, Frame: {d}ms.\n",
.{ render_time_ms, sleep_time_ms, current / std.time.ns_per_ms },
"Render: {d:.3}ms, Sleeping: {d:.3}ms, Frame: {d}ms.\n",
.{ render_time_ms, sleep_time_ms, current_ms },
);
}
+12
View File
@@ -8,6 +8,16 @@ pub fn megaBytes(bytes: usize) usize {
return kiloBytes(bytes) * 1024;
}
const Key = struct {
down: bool,
};
const Keyboard = struct {
r: Key,
q: Key,
space: Key,
};
pub const AppInput = struct {
mouse_present: bool = false,
mouse_left_down: bool = false,
@@ -15,6 +25,8 @@ pub const AppInput = struct {
mouse_y: f64,
scroll_y: f64,
key: Keyboard,
};
/// Buffer the platform creates and hands off to the app
+99 -47
View File
@@ -30,8 +30,28 @@ const Camera = struct {
};
const Brick = struct {
rotating: bool,
pos: V2,
color: u32,
tiles: [4][4]u32,
fn rotate(self: Brick) [4][4]u32 {
var result = [4][4]u32{
.{ 0, 0, 0, 0 },
.{ 0, 0, 0, 0 },
.{ 0, 0, 0, 0 },
.{ 0, 0, 0, 0 },
};
for (0..self.tiles.len) |i| {
const row = self.tiles[i];
for (0..row.len) |j| {
result[j][row.len - i - 1] = self.tiles[i][j];
}
}
return result;
}
};
const State = struct {
@@ -74,8 +94,8 @@ fn fillRect(
if (buffer.width < end_x) end_x = buffer.width;
if (buffer.height < end_y) end_y = buffer.height;
for (@intCast(start_y) .. @intCast(end_y)) |y| {
for (@intCast(start_x) .. @intCast(end_x)) |x| {
for (@intCast(start_y)..@intCast(end_y)) |y| {
for (@intCast(start_x)..@intCast(end_x)) |x| {
const idx: usize = y * @as(usize, @intCast(buffer.width)) + x;
pixels[idx] = color;
@@ -96,91 +116,109 @@ fn fillSquare(
pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBuffer, input: platform.AppInput) void {
var state: *State = @ptrCast(@alignCast(memory.permanent_storage));
const mid = V2{ .x = @floatFromInt(@divTrunc(buffer.width, 2)), .y = @floatFromInt(@divTrunc(buffer.height, 2)), };
const mid = V2{
.x = @floatFromInt(@divTrunc(buffer.width, 2)),
.y = @floatFromInt(@divTrunc(buffer.height, 2)),
};
if (!memory.initialized) {
state.camera.offset = mid;
state.bricks[0] = Brick{
.pos = V2{ .x = 0, .y = 50},
.rotating = false,
.pos = V2{ .x = 0, .y = 50 },
.color = 0xFFFF0000,
.tiles = [4][4]u32{
.{1, 1, 1, 1},
.{1, 0, 0, 0},
.{0, 0, 0, 0},
.{0, 0, 0, 0},
.{ 1, 1, 1, 1 },
.{ 1, 0, 0, 0 },
.{ 0, 0, 0, 0 },
.{ 0, 0, 0, 0 },
},
};
state.bricks[1] = Brick{
.pos = V2{ .x = 32, .y = 50},
.rotating = false,
.pos = V2{ .x = 32, .y = 50 },
.color = 0xFFEF0000,
.tiles = [4][4]u32{
.{2, 2, 2, 0},
.{2, 2, 0, 0},
.{0, 0, 0, 0},
.{0, 0, 0, 0},
.{ 2, 2, 2, 0 },
.{ 2, 2, 0, 0 },
.{ 0, 0, 0, 0 },
.{ 0, 0, 0, 0 },
},
};
state.bricks[2] = Brick{
.pos = V2{ .x = 64, .y = 50},
.rotating = false,
.pos = V2{ .x = 64, .y = 50 },
.color = 0xFFDF0000,
.tiles = [4][4]u32{
.{3, 3, 3, 0},
.{0, 0, 3, 3},
.{0, 0, 0, 0},
.{0, 0, 0, 0},
.{ 3, 3, 3, 0 },
.{ 0, 0, 3, 3 },
.{ 0, 0, 0, 0 },
.{ 0, 0, 0, 0 },
},
};
state.bricks[3] = Brick{
.pos = V2{ .x = 96, .y = 50},
.rotating = false,
.pos = V2{ .x = 96, .y = 50 },
.color = 0xFFCF0000,
.tiles = [4][4]u32{
.{4, 0, 0, 0},
.{4, 4, 4, 0},
.{0, 0, 4, 0},
.{0, 0, 0, 0},
.{ 4, 0, 0, 0 },
.{ 4, 4, 4, 0 },
.{ 0, 0, 4, 0 },
.{ 0, 0, 0, 0 },
},
};
state.bricks[4] = Brick{
.pos = V2{ .x = 128, .y = 50},
.rotating = false,
.pos = V2{ .x = 128, .y = 150 },
.color = 0xFFBF0000,
.tiles = [4][4]u32{
.{5, 5, 5, 5},
.{0, 5, 0, 0},
.{0, 0, 0, 0},
.{0, 0, 0, 0},
.{ 5, 5, 5, 5 },
.{ 0, 5, 0, 0 },
.{ 0, 0, 0, 0 },
.{ 0, 0, 0, 0 },
},
};
state.bricks[5] = Brick{
.pos = V2{ .x = 160, .y = 50},
.rotating = false,
.pos = V2{ .x = 160, .y = 250 },
.color = 0xFFAF0000,
.tiles = [4][4]u32{
.{6, 6, 6, 0},
.{6, 0, 0, 0},
.{6, 0, 0, 0},
.{0, 0, 0, 0},
.{ 6, 6, 6, 0 },
.{ 6, 0, 0, 0 },
.{ 6, 0, 0, 0 },
.{ 0, 0, 0, 0 },
},
};
state.bricks[6] = Brick{
.pos = V2{ .x = 192, .y = 50},
.rotating = false,
.pos = V2{ .x = 192, .y = 350 },
.color = 0xFF9F0000,
.tiles = [4][4]u32{
.{7, 7, 7, 0},
.{7, 7, 7, 0},
.{0, 0, 0, 0},
.{0, 0, 0, 0},
.{ 7, 7, 7, 0 },
.{ 7, 7, 7, 0 },
.{ 0, 0, 0, 0 },
.{ 0, 0, 0, 0 },
},
};
state.bricks[7] = Brick{
.pos = V2{ .x = 224, .y = 50},
.rotating = false,
.pos = V2{ .x = 224, .y = 450 },
.color = 0xFF8F0000,
.tiles = [4][4]u32{
.{8, 8, 8, 0},
.{8, 0, 8, 0},
.{0, 0, 0, 0},
.{0, 0, 0, 0},
.{ 8, 8, 8, 0 },
.{ 8, 0, 8, 0 },
.{ 0, 0, 0, 0 },
.{ 0, 0, 0, 0 },
},
};
memory.initialized = true;
}
@@ -194,6 +232,8 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu
var mouse_on_brick = false;
for (&state.bricks) |*brick| {
brick.rotating = false;
for (brick.tiles, 0..) |row, i| {
for (row, 0..) |tile, j| {
if (tile > 0) {
@@ -208,6 +248,14 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu
if (input.mouse_left_down) {
const diff = mouse_pos.sub(state.mouse_pos);
brick.pos = brick.pos.add(diff);
if (input.key.space.down and !brick.rotating) {
// TODO: Rotate
brick.rotating = true;
const rotated = brick.rotate();
brick.tiles = rotated;
}
mouse_on_brick = true;
}
}
@@ -221,7 +269,6 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu
state.camera.pos = state.camera.pos.add(diff);
}
state.mouse_pos = mouse_pos;
// Render
@@ -255,7 +302,13 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu
.y = @floatFromInt(i * TILE_SIZE),
};
const camera_pos = brick.pos.add(pos).add(state.camera.pos);
fillSquare(buffer, @intFromFloat(camera_pos.x), @intFromFloat(camera_pos.y), 30, 0xFFFF0000);
fillSquare(
buffer,
@intFromFloat(camera_pos.x),
@intFromFloat(camera_pos.y),
30,
if (brick.rotating) 0xFF00FF00 else brick.color ,
);
}
}
}
@@ -263,4 +316,3 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu
state.frame_count += 1;
}