diff --git a/macos/Sources/Ghostty/Surface View/InspectorView.swift b/macos/Sources/Ghostty/Surface View/InspectorView.swift index 2a004ac76..e8eaf3a80 100644 --- a/macos/Sources/Ghostty/Surface View/InspectorView.swift +++ b/macos/Sources/Ghostty/Surface View/InspectorView.swift @@ -269,16 +269,10 @@ extension Ghostty { // Builds up the "input.ScrollMods" bitmask var mods: Int32 = 0 - var x = event.scrollingDeltaX - var y = event.scrollingDeltaY + let x = event.scrollingDeltaX + let y = event.scrollingDeltaY if event.hasPreciseScrollingDeltas { mods = 1 - - // We do a 2x speed multiplier. This is subjective, it "feels" better to me. - x *= 2; - y *= 2; - - // TODO(mitchellh): do we have to scale the x/y here by window scale factor? } // Determine our momentum value diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index b4ad7f885..2fbae8fdf 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -1133,15 +1133,18 @@ pub const Inspector = struct { yoff: f64, mods: input.ScrollMods, ) void { - _ = mods; - self.queueRender(); cimgui.c.ImGui_SetCurrentContext(self.ig_ctx); const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO(); + + // For precision scrolling (trackpads), the values are in pixels which + // scroll way too fast. Scale them down to approximate discrete wheel + // notches. imgui expects 1.0 to scroll ~5 lines of text. + const scale: f64 = if (mods.precision) 0.1 else 1.0; cimgui.c.ImGuiIO_AddMouseWheelEvent( io, - @floatCast(xoff), - @floatCast(yoff), + @floatCast(xoff * scale), + @floatCast(yoff * scale), ); } @@ -1202,10 +1205,11 @@ pub const Inspector = struct { // Determine our delta time const now = try std.time.Instant.now(); io.DeltaTime = if (self.instant) |prev| delta: { - const since_ns = now.since(prev); - const since_s: f32 = @floatFromInt(since_ns / std.time.ns_per_s); + const since_ns: f64 = @floatFromInt(now.since(prev)); + const ns_per_s: f64 = @floatFromInt(std.time.ns_per_s); + const since_s: f32 = @floatCast(since_ns / ns_per_s); break :delta @max(0.00001, since_s); - } else (1 / 60); + } else (1.0 / 60.0); self.instant = now; } }; diff --git a/src/apprt/gtk/class/imgui_widget.zig b/src/apprt/gtk/class/imgui_widget.zig index 79e85fad2..8ad75f5d0 100644 --- a/src/apprt/gtk/class/imgui_widget.zig +++ b/src/apprt/gtk/class/imgui_widget.zig @@ -131,21 +131,17 @@ pub const ImguiWidget = extern struct { /// Initialize the frame. Expects that the context is already current. fn newFrame(self: *Self) void { - // If we can't determine the time since the last frame we default to - // 1/60th of a second. - const default_delta_time = 1 / 60; - const priv = self.private(); - const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO(); // Determine our delta time const now = std.time.Instant.now() catch unreachable; io.DeltaTime = if (priv.instant) |prev| delta: { - const since_ns = now.since(prev); - const since_s: f32 = @floatFromInt(since_ns / std.time.ns_per_s); + const since_ns: f64 = @floatFromInt(now.since(prev)); + const ns_per_s: f64 = @floatFromInt(std.time.ns_per_s); + const since_s: f32 = @floatCast(since_ns / ns_per_s); break :delta @max(0.00001, since_s); - } else default_delta_time; + } else (1.0 / 60.0); priv.instant = now; } diff --git a/src/inspector/Inspector.zig b/src/inspector/Inspector.zig index 156e2cb18..6ffb43d43 100644 --- a/src/inspector/Inspector.zig +++ b/src/inspector/Inspector.zig @@ -583,10 +583,12 @@ fn renderModesWindow(self: *Inspector) void { const tag: terminal.modes.ModeTag = @bitCast(@as(terminal.modes.ModeTag.Backing, field.value)); cimgui.c.ImGui_TableNextRow(); + cimgui.c.ImGui_PushIDInt(@intCast(field.value)); + defer cimgui.c.ImGui_PopID(); { _ = cimgui.c.ImGui_TableSetColumnIndex(0); var value: bool = t.modes.get(@field(terminal.Mode, field.name)); - _ = cimgui.c.ImGui_Checkbox("", &value); + _ = cimgui.c.ImGui_Checkbox("##checkbox", &value); } { _ = cimgui.c.ImGui_TableSetColumnIndex(1); diff --git a/src/renderer/Thread.zig b/src/renderer/Thread.zig index c1b377b3d..d651fed79 100644 --- a/src/renderer/Thread.zig +++ b/src/renderer/Thread.zig @@ -254,7 +254,7 @@ fn threadMain_(self: *Thread) !void { ); // Start the draw timer - self.startDrawTimer(); + self.syncDrawTimer(); // Run log.debug("starting renderer thread", .{}); @@ -292,11 +292,33 @@ fn setQosClass(self: *const Thread) void { } } -fn startDrawTimer(self: *Thread) void { - // If our renderer doesn't support animations then we never run this. - if (!@hasDecl(rendererpkg.Renderer, "hasAnimations")) return; - if (!self.renderer.hasAnimations()) return; - if (self.config.custom_shader_animation == .false) return; +fn syncDrawTimer(self: *Thread) void { + skip: { + // If we have an inspector, we always run the draw timer. + if (self.flags.has_inspector) break :skip; + + // If our renderer supports animations and has them, then we + // always have a draw timer. + if (@hasDecl(rendererpkg.Renderer, "hasAnimations") and + self.renderer.hasAnimations()) + { + break :skip; + } + + // If our config says to always animate, we do so. + switch (self.config.custom_shader_animation) { + // Always animate + .always => break :skip, + // Only when focused + .true => if (self.flags.focused) break :skip, + // Never animate + .false => {}, + } + + // We're skipping the draw timer. Stop it on the next iteration. + self.draw_active = false; + return; + } // Set our active state so it knows we're running. We set this before // even checking the active state in case we have a pending shutdown. @@ -316,11 +338,6 @@ fn startDrawTimer(self: *Thread) void { ); } -fn stopDrawTimer(self: *Thread) void { - // This will stop the draw on the next iteration. - self.draw_active = false; -} - /// Drain the mailbox. fn drainMailbox(self: *Thread) !void { // There's probably a more elegant way to do this... @@ -377,12 +394,10 @@ fn drainMailbox(self: *Thread) !void { // Set it on the renderer try self.renderer.setFocus(v); - if (!v) { - if (self.config.custom_shader_animation != .always) { - // Stop the draw timer - self.stopDrawTimer(); - } + // We always resync our draw timer (may disable it) + self.syncDrawTimer(); + if (!v) { // If we're not focused, then we stop the cursor blink if (self.cursor_c.state() == .active and self.cursor_c_cancel.state() == .dead) @@ -397,9 +412,6 @@ fn drainMailbox(self: *Thread) !void { ); } } else { - // Start the draw timer - self.startDrawTimer(); - // If we're focused, we immediately show the cursor again // and then restart the timer. if (self.cursor_c.state() != .active) { @@ -446,8 +458,7 @@ fn drainMailbox(self: *Thread) !void { // Stop and start the draw timer to capture the new // hasAnimations value. - self.stopDrawTimer(); - self.startDrawTimer(); + self.syncDrawTimer(); }, .search_viewport_matches => |v| { @@ -466,7 +477,12 @@ fn drainMailbox(self: *Thread) !void { self.renderer.search_matches_dirty = true; }, - .inspector => |v| self.flags.has_inspector = v, + .inspector => |v| { + self.flags.has_inspector = v; + // Reset our draw timer state, which might change due + // to the inspector change. + self.syncDrawTimer(); + }, .macos_display_id => |v| { if (@hasDecl(rendererpkg.Renderer, "setMacOSDisplayID")) {