diff --git a/src/Window.zig b/src/Window.zig index 85a16703a..eb5f19e58 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -393,11 +393,6 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window { // .height = @floatToInt(u32, cell_size.height * 4), // }, .{ .width = null, .height = null }); - // Setup our callbacks and user data - winsys.window.setScrollCallback(scrollCallback); - winsys.window.setCursorPosCallback(cursorPosCallback); - winsys.window.setMouseButtonCallback(mouseButtonCallback); - // Call our size callback which handles all our retina setup // Note: this shouldn't be necessary and when we clean up the window // init stuff we should get rid of this. But this is required because @@ -650,28 +645,6 @@ fn queueRender(self: *const Window) !void { try self.renderer_thread.wakeup.send(); } -/// The cursor position from glfw directly is in screen coordinates but -/// all our internal state works in pixels. -fn cursorPosToPixels(self: Window, pos: glfw.Window.CursorPos) glfw.Window.CursorPos { - // The cursor position is in screen coordinates but we - // want it in pixels. we need to get both the size of the - // window in both to get the ratio to make the conversion. - const size = self.window.getSize() catch unreachable; - const fb_size = self.window.getFramebufferSize() catch unreachable; - - // If our framebuffer and screen are the same, then there is no scaling - // happening and we can short-circuit by returning the pos as-is. - if (fb_size.width == size.width and fb_size.height == size.height) - return pos; - - const x_scale = @intToFloat(f64, fb_size.width) / @intToFloat(f64, size.width); - const y_scale = @intToFloat(f64, fb_size.height) / @intToFloat(f64, size.height); - return .{ - .xpos = pos.xpos * x_scale, - .ypos = pos.ypos * y_scale, - }; -} - pub fn sizeCallback(self: *Window, size: apprt.WindowSize) !void { const tracy = trace(@src()); defer tracy.end(); @@ -1049,17 +1022,14 @@ pub fn refreshCallback(self: *Window) !void { try self.queueRender(); } -fn scrollCallback(window: glfw.Window, xoff: f64, yoff: f64) void { +pub fn scrollCallback(self: *Window, xoff: f64, yoff: f64) !void { const tracy = trace(@src()); defer tracy.end(); - const win = window.getUserPointer(Window) orelse return; - // If our dev mode window is visible then we always schedule a render on // cursor move because the cursor might touch our windows. if (DevMode.enabled and DevMode.instance.visible) { - win.queueRender() catch |err| - log.err("error scheduling render timer err={}", .{err}); + try self.queueRender(); // If the mouse event was handled by imgui, ignore it. if (imgui.IO.get()) |io| { @@ -1072,33 +1042,25 @@ fn scrollCallback(window: glfw.Window, xoff: f64, yoff: f64) void { // Positive is up const sign: isize = if (yoff > 0) -1 else 1; - const delta: isize = sign * @max(@divFloor(win.grid_size.rows, 15), 1); + const delta: isize = sign * @max(@divFloor(self.grid_size.rows, 15), 1); log.info("scroll: delta={}", .{delta}); { - win.renderer_state.mutex.lock(); - defer win.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lock(); + defer self.renderer_state.mutex.unlock(); // Modify our viewport, this requires a lock since it affects rendering - win.io.terminal.scrollViewport(.{ .delta = delta }) catch |err| - log.err("error scrolling viewport err={}", .{err}); + try self.io.terminal.scrollViewport(.{ .delta = delta }); // If we're scrolling up or down, then send a mouse event. This requires // a lock since we read terminal state. if (yoff != 0) { - const pos = window.getCursorPos() catch |err| { - log.err("error reading cursor position: {}", .{err}); - return; - }; - - win.mouseReport(if (yoff < 0) .five else .four, .press, win.mouse.mods, pos) catch |err| { - log.err("error reporting mouse event: {}", .{err}); - return; - }; + const pos = try self.windowing_system.getCursorPos(); + try self.mouseReport(if (yoff < 0) .five else .four, .press, self.mouse.mods, pos); } } - win.queueRender() catch unreachable; + try self.queueRender(); } /// The type of action to report for a mouse event. @@ -1109,7 +1071,7 @@ fn mouseReport( button: ?input.MouseButton, action: MouseReportAction, mods: input.Mods, - unscaled_pos: glfw.Window.CursorPos, + pos: apprt.CursorPos, ) !void { // TODO: posToViewport currently clamps to the window boundary, // do we want to not report mouse events at all outside the window? @@ -1137,8 +1099,7 @@ fn mouseReport( } // This format reports X/Y - const pos = self.cursorPosToPixels(unscaled_pos); - const viewport_point = self.posToViewport(pos.xpos, pos.ypos); + const viewport_point = self.posToViewport(pos.x, pos.y); // Record our new point self.mouse.event_point = viewport_point; @@ -1281,8 +1242,8 @@ fn mouseReport( var data: termio.Message.WriteReq.Small.Array = undefined; const resp = try std.fmt.bufPrint(&data, "\x1B[<{d};{d};{d}{c}", .{ button_code, - pos.xpos, - pos.ypos, + pos.x, + pos.y, final, }); @@ -1300,22 +1261,19 @@ fn mouseReport( try self.io_thread.wakeup.send(); } -fn mouseButtonCallback( - window: glfw.Window, - glfw_button: glfw.MouseButton, - glfw_action: glfw.Action, - mods: glfw.Mods, -) void { +pub fn mouseButtonCallback( + self: *Window, + action: input.MouseButtonState, + button: input.MouseButton, + mods: input.Mods, +) !void { const tracy = trace(@src()); defer tracy.end(); - const win = window.getUserPointer(Window) orelse return; - // If our dev mode window is visible then we always schedule a render on // cursor move because the cursor might touch our windows. if (DevMode.enabled and DevMode.instance.visible) { - win.queueRender() catch |err| - log.err("error scheduling render timer in cursorPosCallback err={}", .{err}); + try self.queueRender(); // If the mouse event was handled by imgui, ignore it. if (imgui.IO.get()) |io| { @@ -1323,125 +1281,96 @@ fn mouseButtonCallback( } else |_| {} } - // Convert glfw button to input button - const button: input.MouseButton = switch (glfw_button) { - .left => .left, - .right => .right, - .middle => .middle, - .four => .four, - .five => .five, - .six => .six, - .seven => .seven, - .eight => .eight, - }; - const action: input.MouseButtonState = switch (glfw_action) { - .press => .press, - .release => .release, - else => unreachable, - }; - // Always record our latest mouse state - win.mouse.click_state[@enumToInt(button)] = action; - win.mouse.mods = @bitCast(input.Mods, mods); + self.mouse.click_state[@enumToInt(button)] = action; + self.mouse.mods = @bitCast(input.Mods, mods); - win.renderer_state.mutex.lock(); - defer win.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lock(); + defer self.renderer_state.mutex.unlock(); // Report mouse events if enabled - if (win.io.terminal.modes.mouse_event != .none) { - const pos = window.getCursorPos() catch |err| { - log.err("error reading cursor position: {}", .{err}); - return; - }; + if (self.io.terminal.modes.mouse_event != .none) { + const pos = try self.windowing_system.getCursorPos(); const report_action: MouseReportAction = switch (action) { .press => .press, .release => .release, }; - win.mouseReport( + try self.mouseReport( button, report_action, - win.mouse.mods, + self.mouse.mods, pos, - ) catch |err| { - log.err("error reporting mouse event: {}", .{err}); - return; - }; + ); } // For left button clicks we always record some information for // selection/highlighting purposes. if (button == .left and action == .press) { - const pos = win.cursorPosToPixels(window.getCursorPos() catch |err| { - log.err("error reading cursor position: {}", .{err}); - return; - }); + const pos = try self.windowing_system.getCursorPos(); // If we move our cursor too much between clicks then we reset // the multi-click state. - if (win.mouse.left_click_count > 0) { - const max_distance = win.cell_size.width; + if (self.mouse.left_click_count > 0) { + const max_distance = self.cell_size.width; const distance = @sqrt( - std.math.pow(f64, pos.xpos - win.mouse.left_click_xpos, 2) + - std.math.pow(f64, pos.ypos - win.mouse.left_click_ypos, 2), + std.math.pow(f64, pos.x - self.mouse.left_click_xpos, 2) + + std.math.pow(f64, pos.y - self.mouse.left_click_ypos, 2), ); - if (distance > max_distance) win.mouse.left_click_count = 0; + if (distance > max_distance) self.mouse.left_click_count = 0; } // Store it - const point = win.posToViewport(pos.xpos, pos.ypos); - win.mouse.left_click_point = point.toScreen(&win.io.terminal.screen); - win.mouse.left_click_xpos = pos.xpos; - win.mouse.left_click_ypos = pos.ypos; + const point = self.posToViewport(pos.x, pos.y); + self.mouse.left_click_point = point.toScreen(&self.io.terminal.screen); + self.mouse.left_click_xpos = pos.x; + self.mouse.left_click_ypos = pos.y; // Setup our click counter and timer if (std.time.Instant.now()) |now| { // If we have mouse clicks, then we check if the time elapsed // is less than and our interval and if so, increase the count. - if (win.mouse.left_click_count > 0) { - const since = now.since(win.mouse.left_click_time); - if (since > win.mouse_interval) { - win.mouse.left_click_count = 0; + if (self.mouse.left_click_count > 0) { + const since = now.since(self.mouse.left_click_time); + if (since > self.mouse_interval) { + self.mouse.left_click_count = 0; } } - win.mouse.left_click_time = now; - win.mouse.left_click_count += 1; + self.mouse.left_click_time = now; + self.mouse.left_click_count += 1; // We only support up to triple-clicks. - if (win.mouse.left_click_count > 3) win.mouse.left_click_count = 1; + if (self.mouse.left_click_count > 3) self.mouse.left_click_count = 1; } else |err| { - win.mouse.left_click_count = 1; + self.mouse.left_click_count = 1; log.err("error reading time, mouse multi-click won't work err={}", .{err}); } - switch (win.mouse.left_click_count) { + switch (self.mouse.left_click_count) { // First mouse click, clear selection - 1 => if (win.io.terminal.selection != null) { - win.io.terminal.selection = null; - win.queueRender() catch |err| - log.err("error scheduling render in mouseButtinCallback err={}", .{err}); + 1 => if (self.io.terminal.selection != null) { + self.io.terminal.selection = null; + try self.queueRender(); }, // Double click, select the word under our mouse 2 => { - const sel_ = win.io.terminal.screen.selectWord(win.mouse.left_click_point); + const sel_ = self.io.terminal.screen.selectWord(self.mouse.left_click_point); if (sel_) |sel| { - win.io.terminal.selection = sel; - win.queueRender() catch |err| - log.err("error scheduling render in mouseButtinCallback err={}", .{err}); + self.io.terminal.selection = sel; + try self.queueRender(); } }, // Triple click, select the line under our mouse 3 => { - const sel_ = win.io.terminal.screen.selectLine(win.mouse.left_click_point); + const sel_ = self.io.terminal.screen.selectLine(self.mouse.left_click_point); if (sel_) |sel| { - win.io.terminal.selection = sel; - win.queueRender() catch |err| - log.err("error scheduling render in mouseButtinCallback err={}", .{err}); + self.io.terminal.selection = sel; + try self.queueRender(); } }, @@ -1451,21 +1380,17 @@ fn mouseButtonCallback( } } -fn cursorPosCallback( - window: glfw.Window, - unscaled_xpos: f64, - unscaled_ypos: f64, -) void { +pub fn cursorPosCallback( + self: *Window, + pos: apprt.CursorPos, +) !void { const tracy = trace(@src()); defer tracy.end(); - const win = window.getUserPointer(Window) orelse return; - // If our dev mode window is visible then we always schedule a render on // cursor move because the cursor might touch our windows. if (DevMode.enabled and DevMode.instance.visible) { - win.queueRender() catch |err| - log.err("error scheduling render timer in cursorPosCallback err={}", .{err}); + try self.queueRender(); // If the mouse event was handled by imgui, ignore it. if (imgui.IO.get()) |io| { @@ -1474,25 +1399,19 @@ fn cursorPosCallback( } // We are reading/writing state for the remainder - win.renderer_state.mutex.lock(); - defer win.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lock(); + defer self.renderer_state.mutex.unlock(); // Do a mouse report - if (win.io.terminal.modes.mouse_event != .none) { + if (self.io.terminal.modes.mouse_event != .none) { // We use the first mouse button we find pressed in order to report // since the spec (afaict) does not say... - const button: ?input.MouseButton = button: for (win.mouse.click_state) |state, i| { + const button: ?input.MouseButton = button: for (self.mouse.click_state) |state, i| { if (state == .press) break :button @intToEnum(input.MouseButton, i); } else null; - win.mouseReport(button, .motion, win.mouse.mods, .{ - .xpos = unscaled_xpos, - .ypos = unscaled_ypos, - }) catch |err| { - log.err("error reporting mouse event: {}", .{err}); - return; - }; + try self.mouseReport(button, .motion, self.mouse.mods, pos); // If we're doing mouse motion tracking, we do not support text // selection. @@ -1500,26 +1419,24 @@ fn cursorPosCallback( } // If the cursor isn't clicked currently, it doesn't matter - if (win.mouse.click_state[@enumToInt(input.MouseButton.left)] != .press) return; + if (self.mouse.click_state[@enumToInt(input.MouseButton.left)] != .press) return; // All roads lead to requiring a re-render at this pont. - win.queueRender() catch |err| - log.err("error scheduling render timer in cursorPosCallback err={}", .{err}); + try self.queueRender(); // Convert to pixels from screen coords - const pos = win.cursorPosToPixels(.{ .xpos = unscaled_xpos, .ypos = unscaled_ypos }); - const xpos = pos.xpos; - const ypos = pos.ypos; + const xpos = pos.x; + const ypos = pos.y; // Convert to points - const viewport_point = win.posToViewport(xpos, ypos); - const screen_point = viewport_point.toScreen(&win.io.terminal.screen); + const viewport_point = self.posToViewport(xpos, ypos); + const screen_point = viewport_point.toScreen(&self.io.terminal.screen); // Handle dragging depending on click count - switch (win.mouse.left_click_count) { - 1 => win.dragLeftClickSingle(screen_point, xpos), - 2 => win.dragLeftClickDouble(screen_point), - 3 => win.dragLeftClickTriple(screen_point), + switch (self.mouse.left_click_count) { + 1 => self.dragLeftClickSingle(screen_point, xpos), + 2 => self.dragLeftClickDouble(screen_point), + 3 => self.dragLeftClickTriple(screen_point), else => unreachable, } } diff --git a/src/apprt/glfw.zig b/src/apprt/glfw.zig index e33346fb5..4ca564df0 100644 --- a/src/apprt/glfw.zig +++ b/src/apprt/glfw.zig @@ -91,6 +91,9 @@ pub const Window = struct { win.setKeyCallback(keyCallback); win.setFocusCallback(focusCallback); win.setRefreshCallback(refreshCallback); + win.setScrollCallback(scrollCallback); + win.setCursorPosCallback(cursorPosCallback); + win.setMouseButtonCallback(mouseButtonCallback); // Build our result return Window{ @@ -164,12 +167,45 @@ pub const Window = struct { return apprt.WindowSize{ .width = size.width, .height = size.height }; } + /// Returns the cursor position in scaled pixels relative to the + /// upper-left of the window. + pub fn getCursorPos(self: *const Window) !apprt.CursorPos { + const unscaled_pos = try self.window.getCursorPos(); + const pos = try self.cursorPosToPixels(unscaled_pos); + return apprt.CursorPos{ + .x = @floatCast(f32, pos.xpos), + .y = @floatCast(f32, pos.ypos), + }; + } + /// Set the flag that notes this window should be closed for the next /// iteration of the event loop. pub fn setShouldClose(self: *Window) void { self.window.setShouldClose(true); } + /// The cursor position from glfw directly is in screen coordinates but + /// all our interface works in pixels. + fn cursorPosToPixels(self: *const Window, pos: glfw.Window.CursorPos) !glfw.Window.CursorPos { + // The cursor position is in screen coordinates but we + // want it in pixels. we need to get both the size of the + // window in both to get the ratio to make the conversion. + const size = try self.window.getSize(); + const fb_size = try self.window.getFramebufferSize(); + + // If our framebuffer and screen are the same, then there is no scaling + // happening and we can short-circuit by returning the pos as-is. + if (fb_size.width == size.width and fb_size.height == size.height) + return pos; + + const x_scale = @intToFloat(f64, fb_size.width) / @intToFloat(f64, size.width); + const y_scale = @intToFloat(f64, fb_size.height) / @intToFloat(f64, size.height); + return .{ + .xpos = pos.xpos * x_scale, + .ypos = pos.ypos * y_scale, + }; + } + fn sizeCallback(window: glfw.Window, width: i32, height: i32) void { _ = width; _ = height; @@ -374,4 +410,81 @@ pub const Window = struct { return; }; } + + fn scrollCallback(window: glfw.Window, xoff: f64, yoff: f64) void { + const tracy = trace(@src()); + defer tracy.end(); + + const core_win = window.getUserPointer(CoreWindow) orelse return; + core_win.scrollCallback(xoff, yoff) catch |err| { + log.err("error in scroll callback err={}", .{err}); + return; + }; + } + + fn cursorPosCallback( + window: glfw.Window, + unscaled_xpos: f64, + unscaled_ypos: f64, + ) void { + const tracy = trace(@src()); + defer tracy.end(); + + const core_win = window.getUserPointer(CoreWindow) orelse return; + + // Convert our unscaled x/y to scaled. + const pos = core_win.windowing_system.cursorPosToPixels(.{ + .xpos = unscaled_xpos, + .ypos = unscaled_ypos, + }) catch |err| { + log.err( + "error converting cursor pos to scaled pixels in cursor pos callback err={}", + .{err}, + ); + return; + }; + + core_win.cursorPosCallback(.{ + .x = @floatCast(f32, pos.xpos), + .y = @floatCast(f32, pos.ypos), + }) catch |err| { + log.err("error in cursor pos callback err={}", .{err}); + return; + }; + } + + fn mouseButtonCallback( + window: glfw.Window, + glfw_button: glfw.MouseButton, + glfw_action: glfw.Action, + glfw_mods: glfw.Mods, + ) void { + const tracy = trace(@src()); + defer tracy.end(); + + const core_win = window.getUserPointer(CoreWindow) orelse return; + + // Convert glfw button to input button + const mods = @bitCast(input.Mods, glfw_mods); + const button: input.MouseButton = switch (glfw_button) { + .left => .left, + .right => .right, + .middle => .middle, + .four => .four, + .five => .five, + .six => .six, + .seven => .seven, + .eight => .eight, + }; + const action: input.MouseButtonState = switch (glfw_action) { + .press => .press, + .release => .release, + else => unreachable, + }; + + core_win.mouseButtonCallback(action, button, mods) catch |err| { + log.err("error in scroll callback err={}", .{err}); + return; + }; + } }; diff --git a/src/apprt/structs.zig b/src/apprt/structs.zig index cd0aac678..74fa2fcd1 100644 --- a/src/apprt/structs.zig +++ b/src/apprt/structs.zig @@ -11,3 +11,9 @@ pub const WindowSize = struct { width: u32, height: u32, }; + +/// The position of the cursor in pixels. +pub const CursorPos = struct { + x: f32, + y: f32, +};