Fix frame and grabbing

This commit is contained in:
2026-03-08 16:25:14 +01:00
parent 15d830987e
commit 2959ed9cd0
3 changed files with 123 additions and 74 deletions
+1 -1
View File
@@ -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);
+83 -44
View File
@@ -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();
+39 -29
View File
@@ -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);
}