diff --git a/src/config/Config.zig b/src/config/Config.zig index 81799d167..5d448a250 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -295,6 +295,10 @@ palette: Palette = .{}, /// The color of the cursor. If this is not set, a default will be chosen. @"cursor-color": ?Color = null, +/// Swap the foreground and background colors of the cell under the cursor. This +/// option overrides the `cursor-color` and `cursor-text` options. +@"cursor-invert-fg-bg": bool = false, + /// The opacity level (opposite of transparency) of the cursor. A value of 1 /// is fully opaque and a value of 0 is fully transparent. A value less than 0 /// or greater than 1 will be clamped to the nearest valid value. Note that a diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 6ceaad166..e2caa002b 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -91,6 +91,11 @@ background_color: terminal.color.RGB, /// by a terminal application cursor_color: ?terminal.color.RGB, +/// When `cursor_color` is null, swap the foreground and background colors of +/// the cell under the cursor for the cursor color. Otherwise, use the default +/// foreground color as the cursor color. +cursor_invert: bool, + /// The current frame background color. This is only updated during /// the updateFrame method. current_background_color: terminal.color.RGB, @@ -329,6 +334,7 @@ pub const DerivedConfig = struct { font_features: std.ArrayListUnmanaged([:0]const u8), font_styles: font.CodepointResolver.StyleStatus, cursor_color: ?terminal.color.RGB, + cursor_invert: bool, cursor_opacity: f64, cursor_text: ?terminal.color.RGB, background: terminal.color.RGB, @@ -369,17 +375,21 @@ pub const DerivedConfig = struct { config.link.links.items, ); + const cursor_invert = config.@"cursor-invert-fg-bg"; + return .{ .background_opacity = @max(0, @min(1, config.@"background-opacity")), .font_thicken = config.@"font-thicken", .font_features = font_features, .font_styles = font_styles, - .cursor_color = if (config.@"cursor-color") |col| - col.toTerminalRGB() + .cursor_color = if (!cursor_invert and config.@"cursor-color" != null) + config.@"cursor-color".?.toTerminalRGB() else null, + .cursor_invert = cursor_invert, + .cursor_text = if (config.@"cursor-text") |txt| txt.toTerminalRGB() else @@ -603,6 +613,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal { .foreground_color = options.config.foreground, .background_color = options.config.background, .cursor_color = options.config.cursor_color, + .cursor_invert = options.config.cursor_invert, .current_background_color = options.config.background, // Render state @@ -1900,7 +1911,8 @@ pub fn changeConfig(self: *Metal, config: *DerivedConfig) !void { // Set our new colors self.background_color = config.background; self.foreground_color = config.foreground; - self.cursor_color = config.cursor_color; + self.cursor_invert = config.cursor_invert; + self.cursor_color = if (!config.cursor_invert) config.cursor_color else null; self.config.deinit(); self.config = config.*; @@ -2218,7 +2230,16 @@ fn rebuildCells( // Prepare the cursor cell contents. const style = cursor_style_ orelse break :cursor; - self.addCursor(screen, style); + const cursor_color = self.cursor_color orelse color: { + if (self.cursor_invert) { + const sty = screen.cursor.page_pin.style(screen.cursor.page_cell); + break :color sty.fg(color_palette, self.config.bold_is_bright) orelse self.foreground_color; + } else { + break :color self.foreground_color; + } + }; + + self.addCursor(screen, style, cursor_color); // If the cursor is visible then we set our uniforms. if (style == .block and screen.viewportIsBottom()) { @@ -2226,15 +2247,19 @@ fn rebuildCells( screen.cursor.x, screen.cursor.y, }; - self.uniforms.cursor_color = if (self.config.cursor_text) |txt| .{ - txt.r, - txt.g, - txt.b, - 255, - } else .{ - self.background_color.r, - self.background_color.g, - self.background_color.b, + + const uniform_color = if (self.cursor_invert) blk: { + const sty = screen.cursor.page_pin.style(screen.cursor.page_cell); + break :blk sty.bg(screen.cursor.page_cell, color_palette) orelse self.background_color; + } else if (self.config.cursor_text) |txt| + txt + else + self.background_color; + + self.uniforms.cursor_color = .{ + uniform_color.r, + uniform_color.g, + uniform_color.b, 255, }; } @@ -2495,6 +2520,7 @@ fn addCursor( self: *Metal, screen: *terminal.Screen, cursor_style: renderer.CursorStyle, + cursor_color: terminal.color.RGB, ) void { // Add the cursor. We render the cursor over the wide character if // we're on the wide characer tail. @@ -2510,7 +2536,6 @@ fn addCursor( break :cell .{ prev_cell.wide == .wide, screen.cursor.x - 1 }; }; - const color = self.cursor_color orelse self.foreground_color; const alpha: u8 = if (!self.focused) 255 else alpha: { const alpha = 255 * self.config.cursor_opacity; break :alpha @intFromFloat(@ceil(alpha)); @@ -2540,7 +2565,7 @@ fn addCursor( .mode = .cursor, .grid_pos = .{ x, screen.cursor.y }, .cell_width = if (wide) 2 else 1, - .color = .{ color.r, color.g, color.b, alpha }, + .color = .{ cursor_color.r, cursor_color.g, cursor_color.b, alpha }, .bg_color = .{ 0, 0, 0, 0 }, .glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y }, .glyph_size = .{ render.glyph.width, render.glyph.height }, diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index 2645ebc26..f1d1c3e53 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -95,6 +95,11 @@ background_color: terminal.color.RGB, /// by a terminal application cursor_color: ?terminal.color.RGB, +/// When `cursor_color` is null, swap the foreground and background colors of +/// the cell under the cursor for the cursor color. Otherwise, use the default +/// foreground color as the cursor color. +cursor_invert: bool, + /// Padding options padding: renderer.Options.Padding, @@ -236,6 +241,7 @@ pub const DerivedConfig = struct { font_features: std.ArrayListUnmanaged([:0]const u8), font_styles: font.CodepointResolver.StyleStatus, cursor_color: ?terminal.color.RGB, + cursor_invert: bool, cursor_text: ?terminal.color.RGB, cursor_opacity: f64, background: terminal.color.RGB, @@ -275,17 +281,21 @@ pub const DerivedConfig = struct { config.link.links.items, ); + const cursor_invert = config.@"cursor-invert-fg-bg"; + return .{ .background_opacity = @max(0, @min(1, config.@"background-opacity")), .font_thicken = config.@"font-thicken", .font_features = font_features, .font_styles = font_styles, - .cursor_color = if (config.@"cursor-color") |col| - col.toTerminalRGB() + .cursor_color = if (!cursor_invert and config.@"cursor-color" != null) + config.@"cursor-color".?.toTerminalRGB() else null, + .cursor_invert = cursor_invert, + .cursor_text = if (config.@"cursor-text") |txt| txt.toTerminalRGB() else @@ -355,6 +365,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !OpenGL { .foreground_color = options.config.foreground, .background_color = options.config.background, .cursor_color = options.config.cursor_color, + .cursor_invert = options.config.cursor_invert, .padding = options.padding, .surface_mailbox = options.surface_mailbox, .deferred_font_size = .{ .metrics = grid.metrics }, @@ -1233,20 +1244,30 @@ pub fn rebuildCells( break :cursor_style; } - _ = try self.addCursor(screen, cursor_style); + const cursor_color = self.cursor_color orelse color: { + if (self.cursor_invert) { + const sty = screen.cursor.page_pin.style(screen.cursor.page_cell); + break :color sty.fg(color_palette, self.config.bold_is_bright) orelse self.foreground_color; + } else { + break :color self.foreground_color; + } + }; + + _ = try self.addCursor(screen, cursor_style, cursor_color); if (cursor_cell) |*cell| { if (cell.mode == .fg or cell.mode == .fg_constrained) { - if (self.config.cursor_text) |txt| { - cell.r = txt.r; - cell.g = txt.g; - cell.b = txt.b; - cell.a = 255; - } else { - cell.r = self.background_color.r; - cell.g = self.background_color.g; - cell.b = self.background_color.b; - cell.a = 255; - } + const cell_color = if (self.cursor_invert) blk: { + const sty = screen.cursor.page_pin.style(screen.cursor.page_cell); + break :blk sty.bg(screen.cursor.page_cell, color_palette) orelse self.background_color; + } else if (self.config.cursor_text) |txt| + txt + else + self.background_color; + + cell.r = cell_color.r; + cell.g = cell_color.g; + cell.b = cell_color.b; + cell.a = 255; } try self.cells.append(self.alloc, cell.*); } @@ -1334,6 +1355,7 @@ fn addCursor( self: *OpenGL, screen: *terminal.Screen, cursor_style: renderer.CursorStyle, + cursor_color: terminal.color.RGB, ) !?*const CellProgram.Cell { // Add the cursor. We render the cursor over the wide character if // we're on the wide characer tail. @@ -1349,7 +1371,6 @@ fn addCursor( break :cell .{ prev_cell.wide == .wide, screen.cursor.x - 1 }; }; - const color = self.cursor_color orelse self.foreground_color; const alpha: u8 = if (!self.focused) 255 else alpha: { const alpha = 255 * self.config.cursor_opacity; break :alpha @intFromFloat(@ceil(alpha)); @@ -1380,9 +1401,9 @@ fn addCursor( .grid_col = @intCast(x), .grid_row = @intCast(screen.cursor.y), .grid_width = if (wide) 2 else 1, - .r = color.r, - .g = color.g, - .b = color.b, + .r = cursor_color.r, + .g = cursor_color.g, + .b = cursor_color.b, .a = alpha, .bg_r = 0, .bg_g = 0, @@ -1699,7 +1720,8 @@ pub fn changeConfig(self: *OpenGL, config: *DerivedConfig) !void { // Set our new colors self.background_color = config.background; self.foreground_color = config.foreground; - self.cursor_color = config.cursor_color; + self.cursor_invert = config.cursor_invert; + self.cursor_color = if (!config.cursor_invert) config.cursor_color else null; // Update our uniforms self.deferred_config = .{}; diff --git a/src/termio/Termio.zig b/src/termio/Termio.zig index 9f83e671f..62373fb76 100644 --- a/src/termio/Termio.zig +++ b/src/termio/Termio.zig @@ -82,6 +82,7 @@ pub const DerivedConfig = struct { cursor_style: terminal.CursorStyle, cursor_blink: ?bool, cursor_color: ?configpkg.Config.Color, + cursor_invert: bool, foreground: configpkg.Config.Color, background: configpkg.Config.Color, osc_color_report_format: configpkg.Config.OSCColorReportFormat, @@ -103,6 +104,7 @@ pub const DerivedConfig = struct { .cursor_style = config.@"cursor-style", .cursor_blink = config.@"cursor-style-blink", .cursor_color = config.@"cursor-color", + .cursor_invert = config.@"cursor-invert-fg-bg", .foreground = config.foreground, .background = config.background, .osc_color_report_format = config.@"osc-color-report-format", @@ -176,8 +178,8 @@ pub fn init(self: *Termio, alloc: Allocator, opts: termio.Options) !void { // Create our stream handler. This points to memory in self so it // isn't safe to use until self.* is set. const handler: StreamHandler = handler: { - const default_cursor_color = if (opts.config.cursor_color) |col| - col.toTerminalRGB() + const default_cursor_color = if (!opts.config.cursor_invert and opts.config.cursor_color != null) + opts.config.cursor_color.?.toTerminalRGB() else null; diff --git a/src/termio/stream_handler.zig b/src/termio/stream_handler.zig index ea5562e5a..51326c03c 100644 --- a/src/termio/stream_handler.zig +++ b/src/termio/stream_handler.zig @@ -115,8 +115,8 @@ pub const StreamHandler = struct { self.default_background_color = config.background.toTerminalRGB(); self.default_cursor_style = config.cursor_style; self.default_cursor_blink = config.cursor_blink; - self.default_cursor_color = if (config.cursor_color) |col| - col.toTerminalRGB() + self.default_cursor_color = if (!config.cursor_invert and config.cursor_color != null) + config.cursor_color.?.toTerminalRGB() else null;