From 2959ed9cd0267d42a469277b0780fe2e704fbf62 Mon Sep 17 00:00:00 2001 From: Simen Kirkvik Date: Sun, 8 Mar 2026 16:25:14 +0100 Subject: [PATCH] Fix frame and grabbing --- build.zig | 2 +- src/main.zig | 127 +++++++++++++++++++++++++++++++++------------------ src/root.zig | 68 +++++++++++++++------------ 3 files changed, 123 insertions(+), 74 deletions(-) diff --git a/build.zig b/build.zig index 16d0b48..914ddfb 100644 --- a/build.zig +++ b/build.zig @@ -33,8 +33,8 @@ pub fn build(b: *std.Build) void { exe.root_module.addIncludePath(b.path("src")); exe.root_module.addCSourceFiles(.{ - .files = &.{ "src/xdg-shell.c", }, .language = .c, + .files = &.{ "src/xdg-shell.c", }, }); b.installArtifact(exe); diff --git a/src/main.zig b/src/main.zig index a932155..bc96586 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const builtin = @import("builtin"); const linux = std.os.linux; const puzzle = @import("puzzle"); @@ -46,6 +47,7 @@ const WaylandState = struct { seat: ?*c.wl_seat, shared_memory: ?*c.wl_shm, xdg_wm_base: ?*c.xdg_wm_base, + surface: ?*c.wl_surface, xkb_context: ?*c.xkb_context, xkb_keymap: ?*c.xkb_keymap, @@ -54,6 +56,7 @@ const WaylandState = struct { allocator: std.mem.Allocator, resize: bool, + frame_ready: bool, window_resized: bool, window_width: i32, @@ -107,7 +110,25 @@ fn createSharedMemoryPool(wl_state: WaylandState) !MemoryPool { }; } -fn xdg_wm_base_handle_ping(data: ?*anyopaque, xdg_wm_base: ?*c.struct_xdg_wm_base, serial: u32) callconv(.c) void { +fn wlFrameHandleDone(data: ?*anyopaque, callback: ?*c.wl_callback, callback_data: u32) callconv(.c) void { + _ = callback_data; + + var wl_state: *WaylandState = @ptrCast(@alignCast(data)); + + c.wl_callback_destroy(callback); + + const new_callback: ?*c.wl_callback = c.wl_surface_frame(wl_state.surface); + _ = c.wl_callback_add_listener(new_callback, &frame_callback_listener, wl_state); + + std.debug.print("[Wayland] Frame ready\n", .{}); + wl_state.frame_ready = true; +} + +const frame_callback_listener: c.wl_callback_listener = .{ + .done = wlFrameHandleDone, +}; + +fn xdg_wm_base_handle_ping(data: ?*anyopaque, xdg_wm_base: ?*c.xdg_wm_base, serial: u32) callconv(.c) void { _ = data; std.debug.print("[Wayland] Pong.\n", .{}); c.xdg_wm_base_pong(xdg_wm_base, serial); @@ -118,7 +139,7 @@ const xdg_wm_base_listener: c.xdg_wm_base_listener = .{ }; // configure: ?*const fn (?*anyopaque, ?*struct_xdg_surface, u32) callconv(.c) void = @import("std").mem.zeroes(?*const fn (?*anyopaque, ?*struct_xdg_surface, u32) callconv(.c) void), -fn xdg_surface_handle_configure(data: ?*anyopaque, xdg_surface: ?*c.struct_xdg_surface, serial: u32) callconv(.c) void { +fn xdg_surface_handle_configure(data: ?*anyopaque, xdg_surface: ?*c.xdg_surface, serial: u32) callconv(.c) void { var wl_state: *WaylandState = @ptrCast(@alignCast(data)); c.xdg_surface_ack_configure(xdg_surface, serial); @@ -137,10 +158,10 @@ const xdg_surface_listener: c.xdg_surface_listener = .{ fn xdg_toplevel_handle_configure( data: ?*anyopaque, - xdg_toplevel: ?*c.struct_xdg_toplevel, + xdg_toplevel: ?*c.xdg_toplevel, width: i32, height: i32, - names: [*c]c.struct_wl_array, + names: [*c]c.wl_array, ) callconv(.c) void { var wl_state: *WaylandState = @ptrCast(@alignCast(data)); _ = xdg_toplevel; @@ -154,7 +175,7 @@ fn xdg_toplevel_handle_configure( } } -fn xdg_toplevel_handle_close(data: ?*anyopaque, xdg_toplevel: ?*c.struct_xdg_toplevel) callconv(.c) void { +fn xdg_toplevel_handle_close(data: ?*anyopaque, xdg_toplevel: ?*c.xdg_toplevel) callconv(.c) void { var wl_state: *WaylandState = @ptrCast(@alignCast(data)); _ = xdg_toplevel; @@ -162,20 +183,20 @@ fn xdg_toplevel_handle_close(data: ?*anyopaque, xdg_toplevel: ?*c.struct_xdg_top wl_state.running = false; } -fn xdg_toplevel_handle_configure_bounds(data: ?*anyopaque, toplevel: ?*c.struct_xdg_toplevel, width: i32, height: i32) callconv(.c) void { +fn xdg_toplevel_handle_configure_bounds(data: ?*anyopaque, toplevel: ?*c.xdg_toplevel, width: i32, height: i32) callconv(.c) void { _ = data; _ = toplevel; std.debug.print("[Wayland] xdg toplevel configure bounds: w = {d}, h = {d}.\n", .{ width, height }); } -fn xdg_toplevel_handle_wm_capabilities(data: ?*anyopaque, toplevel: ?*c.struct_xdg_toplevel, names: [*c]c.struct_wl_array) callconv(.c) void { +fn xdg_toplevel_handle_wm_capabilities(data: ?*anyopaque, toplevel: ?*c.xdg_toplevel, names: [*c]c.wl_array) callconv(.c) void { _ = data; _ = toplevel; _ = names; std.debug.print("[Wayland] xdg toplevel wm capabilities.\n", .{}); } -const xdg_toplevel_listener: c.struct_xdg_toplevel_listener = .{ +const xdg_toplevel_listener: c.xdg_toplevel_listener = .{ .configure = xdg_toplevel_handle_configure, .close = xdg_toplevel_handle_close, .configure_bounds = xdg_toplevel_handle_configure_bounds, @@ -194,7 +215,7 @@ const xdg_toplevel_listener: c.struct_xdg_toplevel_listener = .{ /// @param surface surface entered by the pointer /// @param surface_x surface-local x coordinate /// @param surface_y surface-local y coordinate -fn wlPointerHandleEnter(data: ?*anyopaque, wl_pointer: ?*c.struct_wl_pointer, serial: u32, surface: ?*c.struct_wl_surface, surface_x: c.wl_fixed_t, surface_y: c.wl_fixed_t) callconv(.c) void { +fn wlPointerHandleEnter(data: ?*anyopaque, wl_pointer: ?*c.wl_pointer, serial: u32, surface: ?*c.wl_surface, surface_x: c.wl_fixed_t, surface_y: c.wl_fixed_t) callconv(.c) void { const wl_state: *WaylandState = @ptrCast(@alignCast(data)); _ = wl_pointer; @@ -215,7 +236,7 @@ fn wlPointerHandleEnter(data: ?*anyopaque, wl_pointer: ?*c.struct_wl_pointer, se /// the new focus. /// @param serial serial number of the leave event /// @param surface surface left by the pointer -fn wlPointerHandleLeave(data: ?*anyopaque, wl_pointer: ?*c.struct_wl_pointer, serial: u32, surface: ?*c.struct_wl_surface) callconv(.c) void { +fn wlPointerHandleLeave(data: ?*anyopaque, wl_pointer: ?*c.wl_pointer, serial: u32, surface: ?*c.wl_surface) callconv(.c) void { const wl_state: *WaylandState = @ptrCast(@alignCast(data)); _ = wl_pointer; @@ -233,7 +254,7 @@ fn wlPointerHandleLeave(data: ?*anyopaque, wl_pointer: ?*c.struct_wl_pointer, se /// @param time timestamp with millisecond granularity /// @param surface_x surface-local x coordinate /// @param surface_y surface-local y coordinate -fn wlPointerHandleMotion(data: ?*anyopaque, wl_pointer: ?*c.struct_wl_pointer, time: u32, surface_x: c.wl_fixed_t, surface_y: c.wl_fixed_t) callconv(.c) void { +fn wlPointerHandleMotion(data: ?*anyopaque, wl_pointer: ?*c.wl_pointer, time: u32, surface_x: c.wl_fixed_t, surface_y: c.wl_fixed_t) callconv(.c) void { const wl_state: *WaylandState = @ptrCast(@alignCast(data)); _ = wl_pointer; @@ -263,7 +284,7 @@ fn wlPointerHandleMotion(data: ?*anyopaque, wl_pointer: ?*c.struct_wl_pointer, t /// @param time timestamp with millisecond granularity /// @param button button that produced the event /// @param state physical state of the button -fn wlPointerHandleButton(data: ?*anyopaque, wl_pointer: ?*c.struct_wl_pointer, serial: u32, time: u32, button: u32, state: u32) callconv(.c) void { +fn wlPointerHandleButton(data: ?*anyopaque, wl_pointer: ?*c.wl_pointer, serial: u32, time: u32, button: u32, state: u32) callconv(.c) void { const wl_state: *WaylandState = @ptrCast(@alignCast(data)); _ = wl_pointer; @@ -296,7 +317,7 @@ fn wlPointerHandleButton(data: ?*anyopaque, wl_pointer: ?*c.struct_wl_pointer, s /// @param time timestamp with millisecond granularity /// @param axis axis type /// @param value length of vector in surface-local coordinate space -fn wlPointerHandleAxis(data: ?*anyopaque, wl_pointer: ?*c.struct_wl_pointer, time: u32, axis: u32, value: c.wl_fixed_t) callconv(.c) void { +fn wlPointerHandleAxis(data: ?*anyopaque, wl_pointer: ?*c.wl_pointer, time: u32, axis: u32, value: c.wl_fixed_t) callconv(.c) void { const wl_state: *WaylandState = @ptrCast(@alignCast(data)); _ = wl_pointer; @@ -337,7 +358,7 @@ fn wlPointerHandleAxis(data: ?*anyopaque, wl_pointer: ?*c.struct_wl_pointer, tim /// @param axis_source source of the axis event /// @since 5 /// -fn wlPointerHandleAxisSource(data: ?*anyopaque, wl_pointer: ?*c.struct_wl_pointer, axis_source: u32) callconv(.c) void { +fn wlPointerHandleAxisSource(data: ?*anyopaque, wl_pointer: ?*c.wl_pointer, axis_source: u32) callconv(.c) void { _ = data; _ = wl_pointer; _ = axis_source; @@ -362,7 +383,7 @@ fn wlPointerHandleAxisSource(data: ?*anyopaque, wl_pointer: ?*c.struct_wl_pointe /// @param time timestamp with millisecond granularity /// @param axis the axis stopped with this event /// @since 5 -fn wlPointerHandleAxisStop(data: ?*anyopaque, wl_pointer: ?*c.struct_wl_pointer, time: u32, axis: u32) callconv(.c) void { +fn wlPointerHandleAxisStop(data: ?*anyopaque, wl_pointer: ?*c.wl_pointer, time: u32, axis: u32) callconv(.c) void { _ = data; _ = wl_pointer; _ = time; @@ -406,7 +427,7 @@ fn wlPointerHandleAxisStop(data: ?*anyopaque, wl_pointer: ?*c.struct_wl_pointer, /// @param discrete number of steps /// @since 5 /// @deprecated Deprecated since version 8 -fn wlPointerHandleAxisDiscrete(data: ?*anyopaque, wl_pointer: ?*c.struct_wl_pointer, axis: u32, discrete: i32) callconv(.c) void { +fn wlPointerHandleAxisDiscrete(data: ?*anyopaque, wl_pointer: ?*c.wl_pointer, axis: u32, discrete: i32) callconv(.c) void { _ = data; _ = wl_pointer; _ = axis; @@ -439,7 +460,7 @@ fn wlPointerHandleAxisDiscrete(data: ?*anyopaque, wl_pointer: ?*c.struct_wl_poin /// @param axis axis type /// @param value120 scroll distance as fraction of 120 /// @since 8 -fn wlPointerHandleAxisValue120(data: ?*anyopaque, wl_pointer: ?*c.struct_wl_pointer, axis: u32, value120: i32) callconv(.c) void { +fn wlPointerHandleAxisValue120(data: ?*anyopaque, wl_pointer: ?*c.wl_pointer, axis: u32, value120: i32) callconv(.c) void { _ = data; _ = wl_pointer; _ = axis; @@ -486,7 +507,7 @@ fn wlPointerHandleAxisValue120(data: ?*anyopaque, wl_pointer: ?*c.struct_wl_poin /// @param axis axis type /// @param direction physical direction relative to axis motion /// @since 9 -fn wlPointerHandleAxisRelativeDirection(data: ?*anyopaque, wl_pointer: ?*c.struct_wl_pointer, axis: u32, direction: u32) callconv(.c) void { +fn wlPointerHandleAxisRelativeDirection(data: ?*anyopaque, wl_pointer: ?*c.wl_pointer, axis: u32, direction: u32) callconv(.c) void { _ = data; _ = wl_pointer; _ = axis; @@ -530,7 +551,7 @@ fn wlPointerHandleAxisRelativeDirection(data: ?*anyopaque, wl_pointer: ?*c.struc /// and wl_pointer.enter event being split across multiple /// wl_pointer.frame groups. /// @since 5 -fn wlPointerHandleFrame(data: ?*anyopaque, wl_pointer: ?*c.struct_wl_pointer) callconv(.c) void { +fn wlPointerHandleFrame(data: ?*anyopaque, wl_pointer: ?*c.wl_pointer) callconv(.c) void { _ = wl_pointer; const wl_state: *WaylandState = @ptrCast(@alignCast(data)); @@ -577,7 +598,7 @@ fn wlPointerHandleFrame(data: ?*anyopaque, wl_pointer: ?*c.struct_wl_pointer) ca wl_state.pointer_event = std.mem.zeroInit(WlPointerEvent, .{}); } -const wl_pointer_listener: c.struct_wl_pointer_listener = .{ +const wl_pointer_listener: c.wl_pointer_listener = .{ .enter = wlPointerHandleEnter, .leave = wlPointerHandleLeave, .motion = wlPointerHandleMotion, @@ -593,7 +614,7 @@ const wl_pointer_listener: c.struct_wl_pointer_listener = .{ fn wlKeyboardHandleKeymap( data: ?*anyopaque, - wl_keyboard: ?*c.struct_wl_keyboard, + wl_keyboard: ?*c.wl_keyboard, format: u32, fd: i32, size: u32, @@ -606,12 +627,12 @@ fn wlKeyboardHandleKeymap( 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); + const keymap: ?*c.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); + const xkb_state: ?*c.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); @@ -650,7 +671,7 @@ fn wlKeyboardHandleKey(data: ?*anyopaque, keyboard: ?*c.wl_keyboard, serial: u32 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}); + std.debug.print("{s} {any}\n", .{ buf, pressed }); switch (sym) { c.XKB_KEY_Escape => { @@ -706,12 +727,12 @@ const wl_keyboard_listener: c.wl_keyboard_listener = .{ .repeat_info = wlKeyboardHandleRepeatInfo, }; -fn wlSeatHandleCapabilities(data: ?*anyopaque, wl_seat: ?*c.struct_wl_seat, capabilities: u32) callconv(.c) void { +fn wlSeatHandleCapabilities(data: ?*anyopaque, wl_seat: ?*c.wl_seat, capabilities: u32) callconv(.c) void { const wl_state: *WaylandState = @ptrCast(@alignCast(data)); 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); + const pointer: ?*c.wl_pointer = c.wl_seat_get_pointer(wl_seat); _ = c.wl_pointer_add_listener(pointer, &wl_pointer_listener, wl_state); } @@ -722,20 +743,20 @@ fn wlSeatHandleCapabilities(data: ?*anyopaque, wl_seat: ?*c.struct_wl_seat, capa } } -fn wlSeatHandleName(data: ?*anyopaque, wl_seat: ?*c.struct_wl_seat, name: [*c]const u8) callconv(.c) void { +fn wlSeatHandleName(data: ?*anyopaque, wl_seat: ?*c.wl_seat, name: [*c]const u8) callconv(.c) void { _ = data; _ = wl_seat; std.debug.print("[Wayland] wl_seat name {s}\n", .{name}); } -const wl_seat_listener: c.struct_wl_seat_listener = .{ +const wl_seat_listener: c.wl_seat_listener = .{ .capabilities = wlSeatHandleCapabilities, .name = wlSeatHandleName, }; fn registry_handle_global( data: ?*anyopaque, - registry: ?*c.struct_wl_registry, + registry: ?*c.wl_registry, name: u32, interface: [*c]const u8, version: u32, @@ -758,7 +779,7 @@ fn registry_handle_global( } } -const registry_listener: c.struct_wl_registry_listener = .{ +const registry_listener: c.wl_registry_listener = .{ .global = registry_handle_global, }; @@ -799,6 +820,7 @@ pub fn main() u8 { std.debug.print("ERROR: Could not create surface.\n", .{}); return 1; } + wl_state.surface = wl_surface; const xdg_surface = c.xdg_wm_base_get_xdg_surface(wl_state.xdg_wm_base, wl_surface); if (xdg_surface == null) { @@ -816,7 +838,7 @@ pub fn main() u8 { _ = c.xdg_toplevel_add_listener(xdg_toplevel, &xdg_toplevel_listener, &wl_state); - c.xdg_toplevel_set_title(xdg_toplevel, "Oceanbox Tyler"); + c.xdg_toplevel_set_title(xdg_toplevel, "Zig A-Puzzle-A-Day"); c.wl_surface_commit(wl_surface); @@ -834,9 +856,9 @@ pub fn main() u8 { var shared_memory_pool: ?*c.wl_shm_pool = undefined; var shared_memory_pool_data: []u8 = undefined; - var wl_buffer: *c.struct_wl_buffer = undefined; + var wl_buffer: *c.wl_buffer = undefined; - const monitor_update_hz: f64 = 30; + const monitor_update_hz: f64 = 144; const game_update_hz: f64 = monitor_update_hz; const frame_target_time_s: f64 = 1.0 / game_update_hz; const frame_target_time_ms: f64 = std.time.ms_per_s * frame_target_time_s; @@ -858,6 +880,8 @@ pub fn main() u8 { .permanent_storage = app_permanent_storage, }; + var frame_callback_added = false; + var timer = std.time.Timer.start() catch { std.debug.print("[Wayland] ERROR: timer not supported.\n", .{}); return 1; @@ -940,15 +964,17 @@ pub fn main() u8 { const render_start_time: u64 = timer.read(); - wl_state.app_input.delta_time = frame_target_time_s; + if (wl_state.frame_ready) { + wl_state.app_input.delta_time = frame_target_time_s; - const app_offscreen_buffer: puzzle.platform.OffscreenBuffer = .{ - .width = wl_state.window_width, - .height = wl_state.window_height, - .stride = wl_state.window_width * 4, - .data = shared_memory_pool_data, - }; - puzzle.updateAndRender(&app_memory, app_offscreen_buffer, &wl_state.app_input); + const app_offscreen_buffer: puzzle.platform.OffscreenBuffer = .{ + .width = wl_state.window_width, + .height = wl_state.window_height, + .stride = wl_state.window_width * 4, + .data = shared_memory_pool_data, + }; + puzzle.updateAndRender(&app_memory, app_offscreen_buffer, &wl_state.app_input); + } const render_end_time: u64 = timer.read(); const render_time = render_end_time - render_start_time; @@ -963,12 +989,25 @@ pub fn main() u8 { std.debug.print("MISSED FRAME\n", .{}); } - c.wl_surface_attach(wl_surface, wl_buffer, 0, 0); - c.wl_surface_damage_buffer(wl_surface, 0, 0, wl_state.window_width, wl_state.window_height); - c.wl_surface_commit(wl_surface); + if (wl_state.frame_ready) { + c.wl_surface_attach(wl_surface, wl_buffer, 0, 0); + c.wl_surface_damage_buffer(wl_surface, 0, 0, wl_state.window_width, wl_state.window_height); + c.wl_surface_commit(wl_surface); + wl_state.frame_ready = false; + } _ = c.wl_display_roundtrip(display); + if (!frame_callback_added) { + const callback: ?*c.wl_callback = c.wl_surface_frame(wl_surface); + if (callback) |cb| { + const ret: c_int = c.wl_callback_add_listener(cb, &frame_callback_listener, &wl_state); + std.debug.print("[Wayland] Added frame callback with ret {d}.\n", .{ret}); + } + frame_callback_added = true; + wl_state.frame_ready = true; + } + // std.posix.nanosleep(0, std.time.ns_per_ms * 32); const current = timer.lap(); diff --git a/src/root.zig b/src/root.zig index 34d4c13..e9ddfc4 100644 --- a/src/root.zig +++ b/src/root.zig @@ -87,6 +87,7 @@ const State = struct { frame_count: i32, mouse_pos: V2, camera: Camera, + grabbed_brick_index: ?usize, bricks: [8]Brick, }; @@ -238,6 +239,8 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu if (!memory.initialized) { state.camera.offset = mid; + state.grabbed_brick_index = null; + state.bricks[0] = Brick{ .rotating = false, .rotating_time = 0, @@ -350,8 +353,11 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu const mouse_world_pos = mouse_pos.sub(state.camera.pos); - var mouse_on_brick = false; - for (&state.bricks) |*brick| { + if (!input.mouse_left_down) { + state.grabbed_brick_index = null; + } + + for (&state.bricks, 0..) |*brick, brick_index| { if (brick.rotating) { brick.rotating_time += input.delta_time; if (brick.rotating_time > 0.25) { @@ -370,41 +376,45 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu const pos = tile_pos.add(brick.pos); const camera_pos = pos.add(state.camera.pos); - if (pointInsideRect(state.mouse_pos, camera_pos, tile_size)) { - if (input.mouse_left_down) { - const diff = mouse_pos.sub(state.mouse_pos); - brick.pos = brick.pos.add(diff); - - if (keyPressed(&input.key.space)) { - brick.rotating = true; - const rotated = brick.rotate(); - brick.tiles = rotated; - - const bbox = findBrickBoundingBox(brick); - const center = brick.pos.sub(bbox.center); - brick.pos = mouse_world_pos.add(center); + if (input.mouse_left_down) { + if (pointInsideRect(state.mouse_pos, camera_pos, tile_size)) { + if (state.grabbed_brick_index == null) { + state.grabbed_brick_index = brick_index; } - - // Flip - if (keyPressed(&input.key.f)) { - brick.rotating = true; - const flipped = brick.transpose(); - brick.tiles = flipped; - - const bbox = findBrickBoundingBox(brick); - const center = brick.pos.sub(bbox.center); - brick.pos = mouse_world_pos.add(center); - } - - mouse_on_brick = true; } } } } } + + if (brick_index == state.grabbed_brick_index) { + const diff = mouse_pos.sub(state.mouse_pos); + brick.pos = brick.pos.add(diff); + + if (keyPressed(&input.key.space)) { + brick.rotating = true; + const rotated = brick.rotate(); + brick.tiles = rotated; + + const bbox = findBrickBoundingBox(brick); + const center = brick.pos.sub(bbox.center); + brick.pos = mouse_world_pos.add(center); + } + + // Flip + if (keyPressed(&input.key.f)) { + brick.rotating = true; + const flipped = brick.transpose(); + brick.tiles = flipped; + + const bbox = findBrickBoundingBox(brick); + const center = brick.pos.sub(bbox.center); + brick.pos = mouse_world_pos.add(center); + } + } } - if (input.mouse_left_down and !mouse_on_brick) { + if (input.mouse_left_down and state.grabbed_brick_index == null) { const diff = mouse_pos.sub(state.mouse_pos); state.camera.pos = state.camera.pos.add(diff); }