diff --git a/macos/Sources/Features/Terminal/TerminalToolbar.swift b/macos/Sources/Features/Terminal/TerminalToolbar.swift index ba6906a77..87cb6ce4f 100644 --- a/macos/Sources/Features/Terminal/TerminalToolbar.swift +++ b/macos/Sources/Features/Terminal/TerminalToolbar.swift @@ -43,17 +43,17 @@ class TerminalToolbar: NSToolbar, NSToolbarDelegate { item.view = self.titleTextField item.visibilityPriority = .user - // NSToolbarItem.minSize and NSToolbarItem.maxSize are deprecated, and make big ugly - // warnings in Xcode when you use them, but I cannot for the life of me figure out - // how to get this to work with constraints. The behavior isn't the same, instead of - // shrinking the item and clipping the subview, it hides the item as soon as the - // intrinsic size of the subview gets too big for the toolbar width, regardless of - // whether I have constraints set on its width, height, or both :/ - // - // If someone can fix this so we don't have to use deprecated properties: Please do. - item.minSize = NSSize(width: 32, height: 1) - item.maxSize = NSSize(width: 1024, height: self.titleTextField.intrinsicContentSize.height) - + // This ensures the title text field doesn't disappear when shrinking the view + self.titleTextField.translatesAutoresizingMaskIntoConstraints = false + self.titleTextField.setContentHuggingPriority(.defaultLow, for: .horizontal) + self.titleTextField.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) + + // Add constraints to the toolbar item's view + NSLayoutConstraint.activate([ + // Set the height constraint to match the toolbar's height + self.titleTextField.heightAnchor.constraint(equalToConstant: 22), // Adjust as needed + ]) + item.isEnabled = true case .resetZoom: item = NSToolbarItem(itemIdentifier: .resetZoom) @@ -73,23 +73,27 @@ class TerminalToolbar: NSToolbar, NSToolbarDelegate { // getting smaller than the max size so starts clipping. Lucky for us, two of the // built-in spacers plus the un-zoom button item seems to exactly match the space // on the left that's reserved for the window buttons. - return [.titleText, .flexibleSpace, .space, .space, .resetZoom] + return [.flexibleSpace, .titleText, .flexibleSpace] } } /// A label that expands to fit whatever text you put in it and horizontally centers itself in the current window. fileprivate class CenteredDynamicLabel: NSTextField { override func viewDidMoveToSuperview() { - // Truncate the title when it gets too long, cutting it off with an ellipsis. + // Configure the text field + isEditable = false + isBordered = false + drawsBackground = false + alignment = .center + lineBreakMode = .byTruncatingTail cell?.truncatesLastVisibleLine = true - cell?.lineBreakMode = .byCharWrapping - - // Make the text field as small as possible while fitting its text. - setContentHuggingPriority(.required, for: .horizontal) - cell?.alignment = .center - - // We've changed some alignment settings, make sure the layout is updated immediately. - needsLayout = true + + // Use Auto Layout + translatesAutoresizingMaskIntoConstraints = false + + // Set content hugging and compression resistance priorities + setContentHuggingPriority(.defaultLow, for: .horizontal) + setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) } } diff --git a/src/font/sprite.zig b/src/font/sprite.zig index d71e777bf..6485d6008 100644 --- a/src/font/sprite.zig +++ b/src/font/sprite.zig @@ -27,6 +27,8 @@ pub const Sprite = enum(u32) { strikethrough, + overline, + cursor_rect, cursor_hollow_rect, cursor_bar, diff --git a/src/font/sprite/Face.zig b/src/font/sprite/Face.zig index f183192dc..af82bb731 100644 --- a/src/font/sprite/Face.zig +++ b/src/font/sprite/Face.zig @@ -150,6 +150,16 @@ pub fn renderGlyph( self.thickness, ), + .overline => try underline.renderGlyph( + alloc, + atlas, + @enumFromInt(cp), + width, + self.height, + 0, + self.thickness, + ), + .powerline => powerline: { const f: Powerline = .{ .width = width, @@ -166,6 +176,7 @@ pub fn renderGlyph( const Kind = enum { box, underline, + overline, strikethrough, powerline, @@ -179,6 +190,9 @@ const Kind = enum { .underline_curly, => .underline, + .overline, + => .overline, + .strikethrough, => .strikethrough, diff --git a/src/font/sprite/underline.zig b/src/font/sprite/underline.zig index 074212151..e54807bc1 100644 --- a/src/font/sprite/underline.zig +++ b/src/font/sprite/underline.zig @@ -34,6 +34,7 @@ pub fn renderGlyph( .underline_dotted => try drawDotted(alloc, width, line_thickness), .underline_dashed => try drawDashed(alloc, width, line_thickness), .underline_curly => try drawCurly(alloc, width, line_thickness), + .overline => try drawSingle(alloc, width, line_thickness), .strikethrough => try drawSingle(alloc, width, line_thickness), else => unreachable, }; diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 0665c40bc..f586d22b4 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -2492,6 +2492,18 @@ fn rebuildCells( ); }; + if (style.flags.overline) self.addOverline( + @intCast(x), + @intCast(y), + fg, + alpha + ) catch |err| { + log.warn( + "error adding overline to cell, will be invalid x={} y={}, err={}", + .{ x, y, err }, + ); + }; + // If we're at or past the end of our shaper run then // we need to get the next run from the run iterator. if (shaper_cells != null and shaper_cells_i >= shaper_cells.?.len) { @@ -2709,6 +2721,38 @@ fn addUnderline( }); } +/// Add a overline decoration to the specified cell +fn addOverline( + self: *Metal, + x: terminal.size.CellCountInt, + y: terminal.size.CellCountInt, + color: terminal.color.RGB, + alpha: u8, +) !void { + const render = try self.font_grid.renderGlyph( + self.alloc, + font.sprite_index, + @intFromEnum(font.Sprite.overline), + .{ + .cell_width = 1, + .grid_metrics = self.grid_metrics, + }, + ); + + try self.cells.add(self.alloc, .overline, .{ + .mode = .fg, + .grid_pos = .{ @intCast(x), @intCast(y) }, + .constraint_width = 1, + .color = .{ color.r, color.g, color.b, alpha }, + .glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y }, + .glyph_size = .{ render.glyph.width, render.glyph.height }, + .bearings = .{ + @intCast(render.glyph.offset_x), + @intCast(render.glyph.offset_y), + }, + }); +} + /// Add a strikethrough decoration to the specified cell fn addStrikethrough( self: *Metal, diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index 4e3184c28..324fe14b3 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -1586,6 +1586,19 @@ pub fn rebuildCells( ); }; + if (style.flags.overline) self.addOverline( + @intCast(x), + @intCast(y), + fg, + alpha, + bg_color, + ) catch |err| { + log.warn( + "error adding overline to cell, will be invalid x={} y={}, err={}", + .{ x, y, err }, + ); + }; + // If we're at or past the end of our shaper run then // we need to get the next run from the run iterator. if (shaper_cells != null and shaper_cells_i >= shaper_cells.?.len) { @@ -1955,6 +1968,47 @@ fn addUnderline( }); } +/// Add an overline decoration to the specified cell +fn addOverline( + self: *OpenGL, + x: terminal.size.CellCountInt, + y: terminal.size.CellCountInt, + color: terminal.color.RGB, + alpha: u8, + bg: [4]u8, +) !void { + const render = try self.font_grid.renderGlyph( + self.alloc, + font.sprite_index, + @intFromEnum(font.Sprite.overline), + .{ + .cell_width = 1, + .grid_metrics = self.grid_metrics, + }, + ); + + try self.cells.append(self.alloc, .{ + .mode = .fg, + .grid_col = @intCast(x), + .grid_row = @intCast(y), + .grid_width = 1, + .glyph_x = render.glyph.atlas_x, + .glyph_y = render.glyph.atlas_y, + .glyph_width = render.glyph.width, + .glyph_height = render.glyph.height, + .glyph_offset_x = render.glyph.offset_x, + .glyph_offset_y = render.glyph.offset_y, + .r = color.r, + .g = color.g, + .b = color.b, + .a = alpha, + .bg_r = bg[0], + .bg_g = bg[1], + .bg_b = bg[2], + .bg_a = bg[3], + }); +} + /// Add a strikethrough decoration to the specified cell fn addStrikethrough( self: *OpenGL, diff --git a/src/renderer/metal/cell.zig b/src/renderer/metal/cell.zig index 33f781ac1..61b8887fd 100644 --- a/src/renderer/metal/cell.zig +++ b/src/renderer/metal/cell.zig @@ -12,6 +12,7 @@ pub const Key = enum { text, underline, strikethrough, + overline, /// Returns the GPU vertex type for this key. pub fn CellType(self: Key) type { @@ -21,6 +22,7 @@ pub const Key = enum { .text, .underline, .strikethrough, + .overline, => mtl_shaders.CellText, }; } @@ -196,6 +198,7 @@ pub const Contents = struct { .text, .underline, .strikethrough, + .overline, // We have a special list containing the cursor cell at the start // of our fg row pool, so we need to add 1 to the y to get the // correct index. diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig index 7927d0343..f1db3dd52 100644 --- a/src/terminal/Screen.zig +++ b/src/terminal/Screen.zig @@ -1618,6 +1618,14 @@ pub fn setAttribute(self: *Screen, attr: sgr.Attribute) !void { self.cursor.style.underline_color = .none; }, + .overline => { + self.cursor.style.flags.overline = true; + }, + + .reset_overline => { + self.cursor.style.flags.overline = false; + }, + .blink => { self.cursor.style.flags.blink = true; }, diff --git a/src/terminal/sgr.zig b/src/terminal/sgr.zig index 67a4c05ea..7d602714c 100644 --- a/src/terminal/sgr.zig +++ b/src/terminal/sgr.zig @@ -37,6 +37,10 @@ pub const Attribute = union(enum) { @"256_underline_color": u8, reset_underline_color: void, + // Overline the text + overline: void, + reset_overline: void, + /// Blink the text blink: void, reset_blink: void, @@ -237,6 +241,9 @@ pub const Parser = struct { 49 => return Attribute{ .reset_bg = {} }, + 53 => return Attribute{ .overline = {} }, + 55 => return Attribute{ .reset_overline = {} }, + 58 => if (slice.len >= 5 and slice[1] == 2) { self.idx += 4; diff --git a/src/terminal/style.zig b/src/terminal/style.zig index 430fca214..0340047e9 100644 --- a/src/terminal/style.zig +++ b/src/terminal/style.zig @@ -35,6 +35,7 @@ pub const Style = struct { inverse: bool = false, invisible: bool = false, strikethrough: bool = false, + overline: bool = false, underline: sgr.Attribute.Underline = .none, } = .{},