mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 08:16:13 +03:00
Merge pull request #1717 from mitchellh/metalv2
Metal Improvements (plus temporary, purposeful regression)
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@ -939,8 +939,8 @@ pub fn rebuildCells(
|
|||||||
// Determine our x/y range for preedit. We don't want to render anything
|
// Determine our x/y range for preedit. We don't want to render anything
|
||||||
// here because we will render the preedit separately.
|
// here because we will render the preedit separately.
|
||||||
const preedit_range: ?struct {
|
const preedit_range: ?struct {
|
||||||
y: usize,
|
y: terminal.size.CellCountInt,
|
||||||
x: [2]usize,
|
x: [2]terminal.size.CellCountInt,
|
||||||
cp_offset: usize,
|
cp_offset: usize,
|
||||||
} = if (preedit) |preedit_v| preedit: {
|
} = if (preedit) |preedit_v| preedit: {
|
||||||
const range = preedit_v.range(screen.cursor.x, screen.pages.cols - 1);
|
const range = preedit_v.range(screen.cursor.x, screen.pages.cols - 1);
|
||||||
@ -958,7 +958,7 @@ pub fn rebuildCells(
|
|||||||
|
|
||||||
// Build each cell
|
// Build each cell
|
||||||
var row_it = screen.pages.rowIterator(.right_down, .{ .viewport = .{} }, null);
|
var row_it = screen.pages.rowIterator(.right_down, .{ .viewport = .{} }, null);
|
||||||
var y: usize = 0;
|
var y: terminal.size.CellCountInt = 0;
|
||||||
while (row_it.next()) |row| {
|
while (row_it.next()) |row| {
|
||||||
defer y += 1;
|
defer y += 1;
|
||||||
|
|
||||||
|
@ -78,9 +78,13 @@ pub const Preedit = struct {
|
|||||||
/// Range returns the start and end x position of the preedit text
|
/// Range returns the start and end x position of the preedit text
|
||||||
/// along with any codepoint offset necessary to fit the preedit
|
/// along with any codepoint offset necessary to fit the preedit
|
||||||
/// into the available space.
|
/// into the available space.
|
||||||
pub fn range(self: *const Preedit, start: usize, max: usize) struct {
|
pub fn range(
|
||||||
start: usize,
|
self: *const Preedit,
|
||||||
end: usize,
|
start: terminal.size.CellCountInt,
|
||||||
|
max: terminal.size.CellCountInt,
|
||||||
|
) struct {
|
||||||
|
start: terminal.size.CellCountInt,
|
||||||
|
end: terminal.size.CellCountInt,
|
||||||
cp_offset: usize,
|
cp_offset: usize,
|
||||||
} {
|
} {
|
||||||
// If our width is greater than the number of cells we have
|
// If our width is greater than the number of cells we have
|
||||||
@ -92,7 +96,7 @@ pub const Preedit = struct {
|
|||||||
|
|
||||||
// Rebuild our width in reverse order. This is because we want
|
// Rebuild our width in reverse order. This is because we want
|
||||||
// to offset by the end cells, not the start cells (if we have to).
|
// to offset by the end cells, not the start cells (if we have to).
|
||||||
var w: usize = 0;
|
var w: terminal.size.CellCountInt = 0;
|
||||||
for (0..self.codepoints.len) |i| {
|
for (0..self.codepoints.len) |i| {
|
||||||
const reverse_i = self.codepoints.len - i - 1;
|
const reverse_i = self.codepoints.len - i - 1;
|
||||||
const cp = self.codepoints[reverse_i];
|
const cp = self.codepoints[reverse_i];
|
||||||
|
@ -50,6 +50,7 @@ pub const MTLIndexType = enum(c_ulong) {
|
|||||||
/// https://developer.apple.com/documentation/metal/mtlvertexformat?language=objc
|
/// https://developer.apple.com/documentation/metal/mtlvertexformat?language=objc
|
||||||
pub const MTLVertexFormat = enum(c_ulong) {
|
pub const MTLVertexFormat = enum(c_ulong) {
|
||||||
uchar4 = 3,
|
uchar4 = 3,
|
||||||
|
ushort2 = 13,
|
||||||
float2 = 29,
|
float2 = 29,
|
||||||
float4 = 31,
|
float4 = 31,
|
||||||
int2 = 33,
|
int2 = 33,
|
||||||
|
@ -49,6 +49,21 @@ pub fn Buffer(comptime T: type) type {
|
|||||||
self.buffer.msgSend(void, objc.sel("release"), .{});
|
self.buffer.msgSend(void, objc.sel("release"), .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the buffer contents as a slice of T. The contents are
|
||||||
|
/// mutable. The contents may or may not be automatically synced
|
||||||
|
/// depending on the buffer storage mode. See the Metal docs.
|
||||||
|
pub fn contents(self: *Self) ![]T {
|
||||||
|
const len_bytes = self.buffer.getProperty(c_ulong, "length");
|
||||||
|
assert(@mod(len_bytes, @sizeOf(T)) == 0);
|
||||||
|
const len = @divExact(len_bytes, @sizeOf(T));
|
||||||
|
const ptr = self.buffer.msgSend(
|
||||||
|
?[*]T,
|
||||||
|
objc.sel("contents"),
|
||||||
|
.{},
|
||||||
|
).?;
|
||||||
|
return ptr[0..len];
|
||||||
|
}
|
||||||
|
|
||||||
/// Sync new contents to the buffer.
|
/// Sync new contents to the buffer.
|
||||||
pub fn sync(self: *Self, device: objc.Object, data: []const T) !void {
|
pub fn sync(self: *Self, device: objc.Object, data: []const T) !void {
|
||||||
// If we need more bytes than our buffer has, we need to reallocate.
|
// If we need more bytes than our buffer has, we need to reallocate.
|
||||||
|
417
src/renderer/metal/cell.zig
Normal file
417
src/renderer/metal/cell.zig
Normal file
@ -0,0 +1,417 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
const renderer = @import("../../renderer.zig");
|
||||||
|
const terminal = @import("../../terminal/main.zig");
|
||||||
|
const mtl_shaders = @import("shaders.zig");
|
||||||
|
|
||||||
|
/// The possible cell content keys that exist.
|
||||||
|
pub const Key = enum {
|
||||||
|
bg,
|
||||||
|
text,
|
||||||
|
underline,
|
||||||
|
strikethrough,
|
||||||
|
|
||||||
|
/// Returns the GPU vertex type for this key.
|
||||||
|
fn CellType(self: Key) type {
|
||||||
|
return switch (self) {
|
||||||
|
.bg => mtl_shaders.CellBg,
|
||||||
|
|
||||||
|
.text,
|
||||||
|
.underline,
|
||||||
|
.strikethrough,
|
||||||
|
=> mtl_shaders.CellText,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The contents of all the cells in the terminal.
|
||||||
|
///
|
||||||
|
/// The goal of this data structure is to make it efficient for two operations:
|
||||||
|
///
|
||||||
|
/// 1. Setting the contents of a cell by coordinate. More specifically,
|
||||||
|
/// we want to be efficient setting cell contents by row since we
|
||||||
|
/// will be doing row dirty tracking.
|
||||||
|
///
|
||||||
|
/// 2. Syncing the contents of the CPU buffers to GPU buffers. This happens
|
||||||
|
/// every frame and should be as fast as possible.
|
||||||
|
///
|
||||||
|
/// To achieve this, the contents are stored in contiguous arrays by
|
||||||
|
/// GPU vertex type and we have an array of mappings indexed by coordinate
|
||||||
|
/// that map to the index in the GPU vertex array that the content is at.
|
||||||
|
pub const Contents = struct {
|
||||||
|
/// The map contains the mapping of cell content for every cell in the
|
||||||
|
/// terminal to the index in the cells array that the content is at.
|
||||||
|
/// This is ALWAYS sized to exactly (rows * cols) so we want to keep
|
||||||
|
/// this as small as possible.
|
||||||
|
///
|
||||||
|
/// Before any operation, this must be initialized by calling resize
|
||||||
|
/// on the contents.
|
||||||
|
map: []Map,
|
||||||
|
|
||||||
|
/// The grid size of the terminal. This is used to determine the
|
||||||
|
/// map array index from a coordinate.
|
||||||
|
cols: usize,
|
||||||
|
|
||||||
|
/// The actual GPU data (on the CPU) for all the cells in the terminal.
|
||||||
|
/// This only contains the cells that have content set. To determine
|
||||||
|
/// if a cell has content set, we check the map.
|
||||||
|
///
|
||||||
|
/// This data is synced to a buffer on every frame.
|
||||||
|
bgs: std.ArrayListUnmanaged(mtl_shaders.CellBg),
|
||||||
|
text: std.ArrayListUnmanaged(mtl_shaders.CellText),
|
||||||
|
|
||||||
|
/// True when the cursor should be rendered. This is managed by
|
||||||
|
/// the setCursor method and should not be set directly.
|
||||||
|
cursor: bool,
|
||||||
|
|
||||||
|
/// The amount of text elements we reserve at the beginning for
|
||||||
|
/// special elements like the cursor.
|
||||||
|
const text_reserved_len = 1;
|
||||||
|
|
||||||
|
pub fn init(alloc: Allocator) !Contents {
|
||||||
|
const map = try alloc.alloc(Map, 0);
|
||||||
|
errdefer alloc.free(map);
|
||||||
|
|
||||||
|
var result: Contents = .{
|
||||||
|
.map = map,
|
||||||
|
.cols = 0,
|
||||||
|
.bgs = .{},
|
||||||
|
.text = .{},
|
||||||
|
.cursor = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// We preallocate some amount of space for cell contents
|
||||||
|
// we always have as a prefix. For now the current prefix
|
||||||
|
// is length 1: the cursor.
|
||||||
|
try result.text.ensureTotalCapacity(alloc, text_reserved_len);
|
||||||
|
result.text.items.len = text_reserved_len;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Contents, alloc: Allocator) void {
|
||||||
|
alloc.free(self.map);
|
||||||
|
self.bgs.deinit(alloc);
|
||||||
|
self.text.deinit(alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resize the cell contents for the given grid size. This will
|
||||||
|
/// always invalidate the entire cell contents.
|
||||||
|
pub fn resize(
|
||||||
|
self: *Contents,
|
||||||
|
alloc: Allocator,
|
||||||
|
size: renderer.GridSize,
|
||||||
|
) !void {
|
||||||
|
const map = try alloc.alloc(Map, size.rows * size.columns);
|
||||||
|
errdefer alloc.free(map);
|
||||||
|
@memset(map, .{});
|
||||||
|
|
||||||
|
alloc.free(self.map);
|
||||||
|
self.map = map;
|
||||||
|
self.cols = size.columns;
|
||||||
|
self.bgs.clearAndFree(alloc);
|
||||||
|
self.text.shrinkAndFree(alloc, text_reserved_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the slice of fg cell contents to sync with the GPU.
|
||||||
|
pub fn fgCells(self: *const Contents) []const mtl_shaders.CellText {
|
||||||
|
const start: usize = if (self.cursor) 0 else 1;
|
||||||
|
return self.text.items[start..];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the slice of bg cell contents to sync with the GPU.
|
||||||
|
pub fn bgCells(self: *const Contents) []const mtl_shaders.CellBg {
|
||||||
|
return self.bgs.items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the cursor value. If the value is null then the cursor
|
||||||
|
/// is hidden.
|
||||||
|
pub fn setCursor(self: *Contents, v: ?mtl_shaders.CellText) void {
|
||||||
|
const cell = v orelse {
|
||||||
|
self.cursor = false;
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
self.cursor = true;
|
||||||
|
self.text.items[0] = cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the cell contents for the given type and coordinate.
|
||||||
|
pub fn get(
|
||||||
|
self: *const Contents,
|
||||||
|
comptime key: Key,
|
||||||
|
coord: terminal.Coordinate,
|
||||||
|
) ?key.CellType() {
|
||||||
|
const mapping = self.map[self.index(coord)].array.get(key);
|
||||||
|
if (!mapping.set) return null;
|
||||||
|
return switch (key) {
|
||||||
|
.bg => self.bgs.items[mapping.index],
|
||||||
|
|
||||||
|
.text,
|
||||||
|
.underline,
|
||||||
|
.strikethrough,
|
||||||
|
=> self.text.items[mapping.index],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the cell contents for a given type of content at a given
|
||||||
|
/// coordinate (provided by the celll contents).
|
||||||
|
pub fn set(
|
||||||
|
self: *Contents,
|
||||||
|
alloc: Allocator,
|
||||||
|
comptime key: Key,
|
||||||
|
cell: key.CellType(),
|
||||||
|
) !void {
|
||||||
|
const mapping = self.map[
|
||||||
|
self.index(.{
|
||||||
|
.x = cell.grid_pos[0],
|
||||||
|
.y = cell.grid_pos[1],
|
||||||
|
})
|
||||||
|
].array.getPtr(key);
|
||||||
|
|
||||||
|
// Get our list of cells based on the key (comptime).
|
||||||
|
const list = &@field(self, switch (key) {
|
||||||
|
.bg => "bgs",
|
||||||
|
.text, .underline, .strikethrough => "text",
|
||||||
|
});
|
||||||
|
|
||||||
|
// If this content type is already set on this cell, we can
|
||||||
|
// simply update the pre-existing index in the list to the new
|
||||||
|
// contents.
|
||||||
|
if (mapping.set) {
|
||||||
|
list.items[mapping.index] = cell;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise we need to append the new cell to the list.
|
||||||
|
const idx: u31 = @intCast(list.items.len);
|
||||||
|
try list.append(alloc, cell);
|
||||||
|
mapping.* = .{ .set = true, .index = idx };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear all of the cell contents for a given row.
|
||||||
|
///
|
||||||
|
/// Due to the way this works internally, it is best to clear rows
|
||||||
|
/// from the bottom up. This is because when we clear a row, we
|
||||||
|
/// swap remove the last element in the list and then update the
|
||||||
|
/// mapping for the swapped element. If we clear from the top down,
|
||||||
|
/// then we would have to update the mapping for every element in
|
||||||
|
/// the list. If we clear from the bottom up, then we only have to
|
||||||
|
/// update the mapping for the last element in the list.
|
||||||
|
pub fn clear(self: *Contents, y: terminal.size.CellCountInt) void {
|
||||||
|
const start_idx = self.index(.{ .x = 0, .y = y });
|
||||||
|
const end_idx = start_idx + self.cols;
|
||||||
|
const maps = self.map[start_idx..end_idx];
|
||||||
|
for (0..self.cols) |x| {
|
||||||
|
// It is better to clear from the right left due to the same
|
||||||
|
// reasons noted for bottom-up clearing in the doc comment.
|
||||||
|
const rev_x = self.cols - x - 1;
|
||||||
|
const map = &maps[rev_x];
|
||||||
|
|
||||||
|
var it = map.array.iterator();
|
||||||
|
while (it.next()) |entry| {
|
||||||
|
if (!entry.value.set) continue;
|
||||||
|
|
||||||
|
// This value is no longer set
|
||||||
|
entry.value.set = false;
|
||||||
|
|
||||||
|
// Remove the value at index. This does a "swap remove"
|
||||||
|
// which swaps the last element in to this place. This is
|
||||||
|
// important because after this we need to update the mapping
|
||||||
|
// for the swapped element.
|
||||||
|
const original_index = entry.value.index;
|
||||||
|
const coord_: ?terminal.Coordinate = switch (entry.key) {
|
||||||
|
.bg => bg: {
|
||||||
|
_ = self.bgs.swapRemove(original_index);
|
||||||
|
if (self.bgs.items.len == original_index) break :bg null;
|
||||||
|
const new = self.bgs.items[original_index];
|
||||||
|
break :bg .{ .x = new.grid_pos[0], .y = new.grid_pos[1] };
|
||||||
|
},
|
||||||
|
|
||||||
|
.text,
|
||||||
|
.underline,
|
||||||
|
.strikethrough,
|
||||||
|
=> text: {
|
||||||
|
_ = self.text.swapRemove(original_index);
|
||||||
|
if (self.text.items.len == original_index) break :text null;
|
||||||
|
const new = self.text.items[original_index];
|
||||||
|
break :text .{ .x = new.grid_pos[0], .y = new.grid_pos[1] };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// If we have the coordinate of the swapped element, then
|
||||||
|
// we need to update it to point at its new index, which is
|
||||||
|
// the index of the element we just removed.
|
||||||
|
//
|
||||||
|
// The reason we wouldn't have a coordinate is if we are
|
||||||
|
// removing the last element in the array, then nothing
|
||||||
|
// is swapped in and nothing needs to be updated.
|
||||||
|
if (coord_) |coord| {
|
||||||
|
const old_index = switch (entry.key) {
|
||||||
|
.bg => self.bgs.items.len,
|
||||||
|
.text, .underline, .strikethrough => self.text.items.len,
|
||||||
|
};
|
||||||
|
var old_it = self.map[self.index(coord)].array.iterator();
|
||||||
|
while (old_it.next()) |old_entry| {
|
||||||
|
if (old_entry.value.set and
|
||||||
|
old_entry.value.index == old_index)
|
||||||
|
{
|
||||||
|
old_entry.value.index = original_index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index(self: *const Contents, coord: terminal.Coordinate) usize {
|
||||||
|
return coord.y * self.cols + coord.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The mapping of a cell at a specific coordinate to the index in the
|
||||||
|
/// vertex arrays where the cell content is at, if it is set.
|
||||||
|
const Map = struct {
|
||||||
|
/// The set of cell content mappings for a given cell for every
|
||||||
|
/// possible key. This is used to determine if a cell has a given
|
||||||
|
/// type of content (i.e. an underlyine styling) and if so what index
|
||||||
|
/// in the cells array that content is at.
|
||||||
|
const Array = std.EnumArray(Key, Mapping);
|
||||||
|
|
||||||
|
/// The mapping for a given key consists of a bit indicating if the
|
||||||
|
/// content is set and the index in the cells array that the content
|
||||||
|
/// is at. We pack this into a 32-bit integer so we only use 4 bytes
|
||||||
|
/// per possible cell content type.
|
||||||
|
const Mapping = packed struct(u32) {
|
||||||
|
set: bool = false,
|
||||||
|
index: u31 = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The backing array of mappings.
|
||||||
|
array: Array = Array.initFill(.{}),
|
||||||
|
|
||||||
|
pub fn empty(self: *Map) bool {
|
||||||
|
var it = self.array.iterator();
|
||||||
|
while (it.next()) |entry| {
|
||||||
|
if (entry.value.set) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
test Contents {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
const rows = 10;
|
||||||
|
const cols = 10;
|
||||||
|
|
||||||
|
var c = try Contents.init(alloc);
|
||||||
|
try c.resize(alloc, .{ .rows = rows, .columns = cols });
|
||||||
|
defer c.deinit(alloc);
|
||||||
|
|
||||||
|
// Assert that get returns null for everything.
|
||||||
|
for (0..rows) |y| {
|
||||||
|
for (0..cols) |x| {
|
||||||
|
try testing.expect(c.get(.bg, .{
|
||||||
|
.x = @intCast(x),
|
||||||
|
.y = @intCast(y),
|
||||||
|
}) == null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set some contents
|
||||||
|
const cell: mtl_shaders.CellBg = .{
|
||||||
|
.mode = .rgb,
|
||||||
|
.grid_pos = .{ 4, 1 },
|
||||||
|
.cell_width = 1,
|
||||||
|
.color = .{ 0, 0, 0, 1 },
|
||||||
|
};
|
||||||
|
try c.set(alloc, .bg, cell);
|
||||||
|
try testing.expectEqual(cell, c.get(.bg, .{ .x = 4, .y = 1 }).?);
|
||||||
|
|
||||||
|
// Can clear it
|
||||||
|
c.clear(1);
|
||||||
|
for (0..rows) |y| {
|
||||||
|
for (0..cols) |x| {
|
||||||
|
try testing.expect(c.get(.bg, .{
|
||||||
|
.x = @intCast(x),
|
||||||
|
.y = @intCast(y),
|
||||||
|
}) == null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Contents clear retains other content" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
const rows = 10;
|
||||||
|
const cols = 10;
|
||||||
|
|
||||||
|
var c = try Contents.init(alloc);
|
||||||
|
try c.resize(alloc, .{ .rows = rows, .columns = cols });
|
||||||
|
defer c.deinit(alloc);
|
||||||
|
|
||||||
|
// Set some contents
|
||||||
|
const cell1: mtl_shaders.CellBg = .{
|
||||||
|
.mode = .rgb,
|
||||||
|
.grid_pos = .{ 4, 1 },
|
||||||
|
.cell_width = 1,
|
||||||
|
.color = .{ 0, 0, 0, 1 },
|
||||||
|
};
|
||||||
|
const cell2: mtl_shaders.CellBg = .{
|
||||||
|
.mode = .rgb,
|
||||||
|
.grid_pos = .{ 4, 2 },
|
||||||
|
.cell_width = 1,
|
||||||
|
.color = .{ 0, 0, 0, 1 },
|
||||||
|
};
|
||||||
|
try c.set(alloc, .bg, cell1);
|
||||||
|
try c.set(alloc, .bg, cell2);
|
||||||
|
c.clear(1);
|
||||||
|
|
||||||
|
// Row 2 should still be valid.
|
||||||
|
try testing.expectEqual(cell2, c.get(.bg, .{ .x = 4, .y = 2 }).?);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Contents clear last added content" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
const rows = 10;
|
||||||
|
const cols = 10;
|
||||||
|
|
||||||
|
var c = try Contents.init(alloc);
|
||||||
|
try c.resize(alloc, .{ .rows = rows, .columns = cols });
|
||||||
|
defer c.deinit(alloc);
|
||||||
|
|
||||||
|
// Set some contents
|
||||||
|
const cell1: mtl_shaders.CellBg = .{
|
||||||
|
.mode = .rgb,
|
||||||
|
.grid_pos = .{ 4, 1 },
|
||||||
|
.cell_width = 1,
|
||||||
|
.color = .{ 0, 0, 0, 1 },
|
||||||
|
};
|
||||||
|
const cell2: mtl_shaders.CellBg = .{
|
||||||
|
.mode = .rgb,
|
||||||
|
.grid_pos = .{ 4, 2 },
|
||||||
|
.cell_width = 1,
|
||||||
|
.color = .{ 0, 0, 0, 1 },
|
||||||
|
};
|
||||||
|
try c.set(alloc, .bg, cell1);
|
||||||
|
try c.set(alloc, .bg, cell2);
|
||||||
|
c.clear(2);
|
||||||
|
|
||||||
|
// Row 2 should still be valid.
|
||||||
|
try testing.expectEqual(cell1, c.get(.bg, .{ .x = 4, .y = 1 }).?);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Contents.Map size" {
|
||||||
|
// We want to be mindful of when this increases because it affects
|
||||||
|
// renderer memory significantly.
|
||||||
|
try std.testing.expectEqual(@as(usize, 16), @sizeOf(Contents.Map));
|
||||||
|
}
|
@ -16,7 +16,11 @@ pub const Shaders = struct {
|
|||||||
/// The cell shader is the shader used to render the terminal cells.
|
/// The cell shader is the shader used to render the terminal cells.
|
||||||
/// It is a single shader that is used for both the background and
|
/// It is a single shader that is used for both the background and
|
||||||
/// foreground.
|
/// foreground.
|
||||||
cell_pipeline: objc.Object,
|
cell_text_pipeline: objc.Object,
|
||||||
|
|
||||||
|
/// The cell background shader is the shader used to render the
|
||||||
|
/// background of terminal cells.
|
||||||
|
cell_bg_pipeline: objc.Object,
|
||||||
|
|
||||||
/// The image shader is the shader used to render images for things
|
/// The image shader is the shader used to render images for things
|
||||||
/// like the Kitty image protocol.
|
/// like the Kitty image protocol.
|
||||||
@ -40,8 +44,11 @@ pub const Shaders = struct {
|
|||||||
const library = try initLibrary(device);
|
const library = try initLibrary(device);
|
||||||
errdefer library.msgSend(void, objc.sel("release"), .{});
|
errdefer library.msgSend(void, objc.sel("release"), .{});
|
||||||
|
|
||||||
const cell_pipeline = try initCellPipeline(device, library);
|
const cell_text_pipeline = try initCellTextPipeline(device, library);
|
||||||
errdefer cell_pipeline.msgSend(void, objc.sel("release"), .{});
|
errdefer cell_text_pipeline.msgSend(void, objc.sel("release"), .{});
|
||||||
|
|
||||||
|
const cell_bg_pipeline = try initCellBgPipeline(device, library);
|
||||||
|
errdefer cell_bg_pipeline.msgSend(void, objc.sel("release"), .{});
|
||||||
|
|
||||||
const image_pipeline = try initImagePipeline(device, library);
|
const image_pipeline = try initImagePipeline(device, library);
|
||||||
errdefer image_pipeline.msgSend(void, objc.sel("release"), .{});
|
errdefer image_pipeline.msgSend(void, objc.sel("release"), .{});
|
||||||
@ -65,7 +72,8 @@ pub const Shaders = struct {
|
|||||||
|
|
||||||
return .{
|
return .{
|
||||||
.library = library,
|
.library = library,
|
||||||
.cell_pipeline = cell_pipeline,
|
.cell_text_pipeline = cell_text_pipeline,
|
||||||
|
.cell_bg_pipeline = cell_bg_pipeline,
|
||||||
.image_pipeline = image_pipeline,
|
.image_pipeline = image_pipeline,
|
||||||
.post_pipelines = post_pipelines,
|
.post_pipelines = post_pipelines,
|
||||||
};
|
};
|
||||||
@ -73,7 +81,8 @@ pub const Shaders = struct {
|
|||||||
|
|
||||||
pub fn deinit(self: *Shaders, alloc: Allocator) void {
|
pub fn deinit(self: *Shaders, alloc: Allocator) void {
|
||||||
// Release our primary shaders
|
// Release our primary shaders
|
||||||
self.cell_pipeline.msgSend(void, objc.sel("release"), .{});
|
self.cell_text_pipeline.msgSend(void, objc.sel("release"), .{});
|
||||||
|
self.cell_bg_pipeline.msgSend(void, objc.sel("release"), .{});
|
||||||
self.image_pipeline.msgSend(void, objc.sel("release"), .{});
|
self.image_pipeline.msgSend(void, objc.sel("release"), .{});
|
||||||
self.library.msgSend(void, objc.sel("release"), .{});
|
self.library.msgSend(void, objc.sel("release"), .{});
|
||||||
|
|
||||||
@ -87,25 +96,6 @@ pub const Shaders = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// This is a single parameter for the terminal cell shader.
|
|
||||||
pub const Cell = extern struct {
|
|
||||||
mode: Mode,
|
|
||||||
grid_pos: [2]f32,
|
|
||||||
glyph_pos: [2]u32 = .{ 0, 0 },
|
|
||||||
glyph_size: [2]u32 = .{ 0, 0 },
|
|
||||||
glyph_offset: [2]i32 = .{ 0, 0 },
|
|
||||||
color: [4]u8,
|
|
||||||
bg_color: [4]u8,
|
|
||||||
cell_width: u8,
|
|
||||||
|
|
||||||
pub const Mode = enum(u8) {
|
|
||||||
bg = 1,
|
|
||||||
fg = 2,
|
|
||||||
fg_constrained = 3,
|
|
||||||
fg_color = 7,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Single parameter for the image shader. See shader for field details.
|
/// Single parameter for the image shader. See shader for field details.
|
||||||
pub const Image = extern struct {
|
pub const Image = extern struct {
|
||||||
grid_pos: [2]f32,
|
grid_pos: [2]f32,
|
||||||
@ -126,6 +116,10 @@ pub const Uniforms = extern struct {
|
|||||||
/// The minimum contrast ratio for text. The contrast ratio is calculated
|
/// The minimum contrast ratio for text. The contrast ratio is calculated
|
||||||
/// according to the WCAG 2.0 spec.
|
/// according to the WCAG 2.0 spec.
|
||||||
min_contrast: f32,
|
min_contrast: f32,
|
||||||
|
|
||||||
|
/// The cursor position and color.
|
||||||
|
cursor_pos: [2]u16,
|
||||||
|
cursor_color: [4]u8,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The uniforms used for custom postprocess shaders.
|
/// The uniforms used for custom postprocess shaders.
|
||||||
@ -294,12 +288,31 @@ fn initPostPipeline(
|
|||||||
return pipeline_state;
|
return pipeline_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This is a single parameter for the terminal cell shader.
|
||||||
|
pub const CellText = extern struct {
|
||||||
|
mode: Mode,
|
||||||
|
glyph_pos: [2]u32 = .{ 0, 0 },
|
||||||
|
glyph_size: [2]u32 = .{ 0, 0 },
|
||||||
|
glyph_offset: [2]i32 = .{ 0, 0 },
|
||||||
|
color: [4]u8,
|
||||||
|
bg_color: [4]u8,
|
||||||
|
grid_pos: [2]u16,
|
||||||
|
cell_width: u8,
|
||||||
|
|
||||||
|
pub const Mode = enum(u8) {
|
||||||
|
fg = 1,
|
||||||
|
fg_constrained = 2,
|
||||||
|
fg_color = 3,
|
||||||
|
cursor = 4,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
/// Initialize the cell render pipeline for our shader library.
|
/// Initialize the cell render pipeline for our shader library.
|
||||||
fn initCellPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
fn initCellTextPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
||||||
// Get our vertex and fragment functions
|
// Get our vertex and fragment functions
|
||||||
const func_vert = func_vert: {
|
const func_vert = func_vert: {
|
||||||
const str = try macos.foundation.String.createWithBytes(
|
const str = try macos.foundation.String.createWithBytes(
|
||||||
"uber_vertex",
|
"cell_text_vertex",
|
||||||
.utf8,
|
.utf8,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
@ -310,7 +323,7 @@ fn initCellPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
|||||||
};
|
};
|
||||||
const func_frag = func_frag: {
|
const func_frag = func_frag: {
|
||||||
const str = try macos.foundation.String.createWithBytes(
|
const str = try macos.foundation.String.createWithBytes(
|
||||||
"uber_fragment",
|
"cell_text_fragment",
|
||||||
.utf8,
|
.utf8,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
@ -344,7 +357,7 @@ fn initCellPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
|||||||
);
|
);
|
||||||
|
|
||||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uchar));
|
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uchar));
|
||||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(Cell, "mode")));
|
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellText, "mode")));
|
||||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@ -354,8 +367,8 @@ fn initCellPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
|||||||
.{@as(c_ulong, 1)},
|
.{@as(c_ulong, 1)},
|
||||||
);
|
);
|
||||||
|
|
||||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.float2));
|
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.ushort2));
|
||||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(Cell, "grid_pos")));
|
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellText, "grid_pos")));
|
||||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@ -366,7 +379,7 @@ fn initCellPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
|||||||
);
|
);
|
||||||
|
|
||||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uint2));
|
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uint2));
|
||||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(Cell, "glyph_pos")));
|
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellText, "glyph_pos")));
|
||||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@ -377,7 +390,7 @@ fn initCellPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
|||||||
);
|
);
|
||||||
|
|
||||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uint2));
|
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uint2));
|
||||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(Cell, "glyph_size")));
|
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellText, "glyph_size")));
|
||||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@ -388,7 +401,7 @@ fn initCellPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
|||||||
);
|
);
|
||||||
|
|
||||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.int2));
|
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.int2));
|
||||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(Cell, "glyph_offset")));
|
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellText, "glyph_offset")));
|
||||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@ -399,7 +412,7 @@ fn initCellPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
|||||||
);
|
);
|
||||||
|
|
||||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uchar4));
|
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uchar4));
|
||||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(Cell, "color")));
|
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellText, "color")));
|
||||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@ -410,7 +423,7 @@ fn initCellPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
|||||||
);
|
);
|
||||||
|
|
||||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uchar4));
|
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uchar4));
|
||||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(Cell, "bg_color")));
|
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellText, "bg_color")));
|
||||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@ -421,7 +434,7 @@ fn initCellPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
|||||||
);
|
);
|
||||||
|
|
||||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uchar));
|
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uchar));
|
||||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(Cell, "cell_width")));
|
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellText, "cell_width")));
|
||||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -436,7 +449,174 @@ fn initCellPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
|||||||
|
|
||||||
// Access each Cell per instance, not per vertex.
|
// Access each Cell per instance, not per vertex.
|
||||||
layout.setProperty("stepFunction", @intFromEnum(mtl.MTLVertexStepFunction.per_instance));
|
layout.setProperty("stepFunction", @intFromEnum(mtl.MTLVertexStepFunction.per_instance));
|
||||||
layout.setProperty("stride", @as(c_ulong, @sizeOf(Cell)));
|
layout.setProperty("stride", @as(c_ulong, @sizeOf(CellText)));
|
||||||
|
}
|
||||||
|
|
||||||
|
break :vertex_desc desc;
|
||||||
|
};
|
||||||
|
defer vertex_desc.msgSend(void, objc.sel("release"), .{});
|
||||||
|
|
||||||
|
// Create our descriptor
|
||||||
|
const desc = init: {
|
||||||
|
const Class = objc.getClass("MTLRenderPipelineDescriptor").?;
|
||||||
|
const id_alloc = Class.msgSend(objc.Object, objc.sel("alloc"), .{});
|
||||||
|
const id_init = id_alloc.msgSend(objc.Object, objc.sel("init"), .{});
|
||||||
|
break :init id_init;
|
||||||
|
};
|
||||||
|
defer desc.msgSend(void, objc.sel("release"), .{});
|
||||||
|
|
||||||
|
// Set our properties
|
||||||
|
desc.setProperty("vertexFunction", func_vert);
|
||||||
|
desc.setProperty("fragmentFunction", func_frag);
|
||||||
|
desc.setProperty("vertexDescriptor", vertex_desc);
|
||||||
|
|
||||||
|
// Set our color attachment
|
||||||
|
const attachments = objc.Object.fromId(desc.getProperty(?*anyopaque, "colorAttachments"));
|
||||||
|
{
|
||||||
|
const attachment = attachments.msgSend(
|
||||||
|
objc.Object,
|
||||||
|
objc.sel("objectAtIndexedSubscript:"),
|
||||||
|
.{@as(c_ulong, 0)},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Value is MTLPixelFormatBGRA8Unorm
|
||||||
|
attachment.setProperty("pixelFormat", @as(c_ulong, 80));
|
||||||
|
|
||||||
|
// Blending. This is required so that our text we render on top
|
||||||
|
// of our drawable properly blends into the bg.
|
||||||
|
attachment.setProperty("blendingEnabled", true);
|
||||||
|
attachment.setProperty("rgbBlendOperation", @intFromEnum(mtl.MTLBlendOperation.add));
|
||||||
|
attachment.setProperty("alphaBlendOperation", @intFromEnum(mtl.MTLBlendOperation.add));
|
||||||
|
attachment.setProperty("sourceRGBBlendFactor", @intFromEnum(mtl.MTLBlendFactor.one));
|
||||||
|
attachment.setProperty("sourceAlphaBlendFactor", @intFromEnum(mtl.MTLBlendFactor.one));
|
||||||
|
attachment.setProperty("destinationRGBBlendFactor", @intFromEnum(mtl.MTLBlendFactor.one_minus_source_alpha));
|
||||||
|
attachment.setProperty("destinationAlphaBlendFactor", @intFromEnum(mtl.MTLBlendFactor.one_minus_source_alpha));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make our state
|
||||||
|
var err: ?*anyopaque = null;
|
||||||
|
const pipeline_state = device.msgSend(
|
||||||
|
objc.Object,
|
||||||
|
objc.sel("newRenderPipelineStateWithDescriptor:error:"),
|
||||||
|
.{ desc, &err },
|
||||||
|
);
|
||||||
|
try checkError(err);
|
||||||
|
errdefer pipeline_state.msgSend(void, objc.sel("release"), .{});
|
||||||
|
|
||||||
|
return pipeline_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is a single parameter for the cell bg shader.
|
||||||
|
pub const CellBg = extern struct {
|
||||||
|
mode: Mode,
|
||||||
|
grid_pos: [2]u16,
|
||||||
|
color: [4]u8,
|
||||||
|
cell_width: u8,
|
||||||
|
|
||||||
|
pub const Mode = enum(u8) {
|
||||||
|
rgb = 1,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Initialize the cell background render pipeline for our shader library.
|
||||||
|
fn initCellBgPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
||||||
|
// Get our vertex and fragment functions
|
||||||
|
const func_vert = func_vert: {
|
||||||
|
const str = try macos.foundation.String.createWithBytes(
|
||||||
|
"cell_bg_vertex",
|
||||||
|
.utf8,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
defer str.release();
|
||||||
|
|
||||||
|
const ptr = library.msgSend(?*anyopaque, objc.sel("newFunctionWithName:"), .{str});
|
||||||
|
break :func_vert objc.Object.fromId(ptr.?);
|
||||||
|
};
|
||||||
|
defer func_vert.msgSend(void, objc.sel("release"), .{});
|
||||||
|
const func_frag = func_frag: {
|
||||||
|
const str = try macos.foundation.String.createWithBytes(
|
||||||
|
"cell_bg_fragment",
|
||||||
|
.utf8,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
defer str.release();
|
||||||
|
|
||||||
|
const ptr = library.msgSend(?*anyopaque, objc.sel("newFunctionWithName:"), .{str});
|
||||||
|
break :func_frag objc.Object.fromId(ptr.?);
|
||||||
|
};
|
||||||
|
defer func_frag.msgSend(void, objc.sel("release"), .{});
|
||||||
|
|
||||||
|
// Create the vertex descriptor. The vertex descriptor describes the
|
||||||
|
// data layout of the vertex inputs. We use indexed (or "instanced")
|
||||||
|
// rendering, so this makes it so that each instance gets a single
|
||||||
|
// Cell as input.
|
||||||
|
const vertex_desc = vertex_desc: {
|
||||||
|
const desc = init: {
|
||||||
|
const Class = objc.getClass("MTLVertexDescriptor").?;
|
||||||
|
const id_alloc = Class.msgSend(objc.Object, objc.sel("alloc"), .{});
|
||||||
|
const id_init = id_alloc.msgSend(objc.Object, objc.sel("init"), .{});
|
||||||
|
break :init id_init;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Our attributes are the fields of the input
|
||||||
|
const attrs = objc.Object.fromId(desc.getProperty(?*anyopaque, "attributes"));
|
||||||
|
{
|
||||||
|
const attr = attrs.msgSend(
|
||||||
|
objc.Object,
|
||||||
|
objc.sel("objectAtIndexedSubscript:"),
|
||||||
|
.{@as(c_ulong, 0)},
|
||||||
|
);
|
||||||
|
|
||||||
|
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uchar));
|
||||||
|
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellBg, "mode")));
|
||||||
|
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const attr = attrs.msgSend(
|
||||||
|
objc.Object,
|
||||||
|
objc.sel("objectAtIndexedSubscript:"),
|
||||||
|
.{@as(c_ulong, 1)},
|
||||||
|
);
|
||||||
|
|
||||||
|
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.ushort2));
|
||||||
|
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellBg, "grid_pos")));
|
||||||
|
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const attr = attrs.msgSend(
|
||||||
|
objc.Object,
|
||||||
|
objc.sel("objectAtIndexedSubscript:"),
|
||||||
|
.{@as(c_ulong, 2)},
|
||||||
|
);
|
||||||
|
|
||||||
|
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uchar));
|
||||||
|
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellBg, "cell_width")));
|
||||||
|
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const attr = attrs.msgSend(
|
||||||
|
objc.Object,
|
||||||
|
objc.sel("objectAtIndexedSubscript:"),
|
||||||
|
.{@as(c_ulong, 3)},
|
||||||
|
);
|
||||||
|
|
||||||
|
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uchar4));
|
||||||
|
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellBg, "color")));
|
||||||
|
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The layout describes how and when we fetch the next vertex input.
|
||||||
|
const layouts = objc.Object.fromId(desc.getProperty(?*anyopaque, "layouts"));
|
||||||
|
{
|
||||||
|
const layout = layouts.msgSend(
|
||||||
|
objc.Object,
|
||||||
|
objc.sel("objectAtIndexedSubscript:"),
|
||||||
|
.{@as(c_ulong, 0)},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Access each Cell per instance, not per vertex.
|
||||||
|
layout.setProperty("stepFunction", @intFromEnum(mtl.MTLVertexStepFunction.per_instance));
|
||||||
|
layout.setProperty("stride", @as(c_ulong, @sizeOf(CellBg)));
|
||||||
}
|
}
|
||||||
|
|
||||||
break :vertex_desc desc;
|
break :vertex_desc desc;
|
||||||
@ -664,11 +844,20 @@ fn checkError(err_: ?*anyopaque) !void {
|
|||||||
// on macOS 12 or Apple Silicon macOS 13.
|
// on macOS 12 or Apple Silicon macOS 13.
|
||||||
//
|
//
|
||||||
// To be safe, we put this test in here.
|
// To be safe, we put this test in here.
|
||||||
test "Cell offsets" {
|
test "CellText offsets" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const alignment = @alignOf(Cell);
|
const alignment = @alignOf(CellText);
|
||||||
inline for (@typeInfo(Cell).Struct.fields) |field| {
|
inline for (@typeInfo(CellText).Struct.fields) |field| {
|
||||||
const offset = @offsetOf(Cell, field.name);
|
const offset = @offsetOf(CellText, field.name);
|
||||||
|
try testing.expectEqual(0, @mod(offset, alignment));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "CellBg offsets" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alignment = @alignOf(CellBg);
|
||||||
|
inline for (@typeInfo(CellBg).Struct.fields) |field| {
|
||||||
|
const offset = @offsetOf(CellBg, field.name);
|
||||||
try testing.expectEqual(0, @mod(offset, alignment));
|
try testing.expectEqual(0, @mod(offset, alignment));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,56 +1,11 @@
|
|||||||
using namespace metal;
|
using namespace metal;
|
||||||
|
|
||||||
// The possible modes that a shader can take.
|
|
||||||
enum Mode : uint8_t {
|
|
||||||
MODE_BG = 1u,
|
|
||||||
MODE_FG = 2u,
|
|
||||||
MODE_FG_CONSTRAINED = 3u,
|
|
||||||
MODE_FG_COLOR = 7u,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Uniforms {
|
struct Uniforms {
|
||||||
float4x4 projection_matrix;
|
float4x4 projection_matrix;
|
||||||
float2 cell_size;
|
float2 cell_size;
|
||||||
float min_contrast;
|
float min_contrast;
|
||||||
};
|
ushort2 cursor_pos;
|
||||||
|
uchar4 cursor_color;
|
||||||
struct VertexIn {
|
|
||||||
// The mode for this cell.
|
|
||||||
uint8_t mode [[attribute(0)]];
|
|
||||||
|
|
||||||
// The grid coordinates (x, y) where x < columns and y < rows
|
|
||||||
float2 grid_pos [[attribute(1)]];
|
|
||||||
|
|
||||||
// The width of the cell in cells (i.e. 2 for double-wide).
|
|
||||||
uint8_t cell_width [[attribute(6)]];
|
|
||||||
|
|
||||||
// The color. For BG modes, this is the bg color, for FG modes this is
|
|
||||||
// the text color. For styles, this is the color of the style.
|
|
||||||
uchar4 color [[attribute(5)]];
|
|
||||||
|
|
||||||
// The fields below are present only when rendering text (fg mode)
|
|
||||||
|
|
||||||
// The background color of the cell. This is used to determine if
|
|
||||||
// we need to render the text with a different color to ensure
|
|
||||||
// contrast.
|
|
||||||
uchar4 bg_color [[attribute(7)]];
|
|
||||||
|
|
||||||
// The position of the glyph in the texture (x,y)
|
|
||||||
uint2 glyph_pos [[attribute(2)]];
|
|
||||||
|
|
||||||
// The size of the glyph in the texture (w,h)
|
|
||||||
uint2 glyph_size [[attribute(3)]];
|
|
||||||
|
|
||||||
// The left and top bearings for the glyph (x,y)
|
|
||||||
int2 glyph_offset [[attribute(4)]];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct VertexOut {
|
|
||||||
float4 position [[position]];
|
|
||||||
float2 cell_size;
|
|
||||||
uint8_t mode;
|
|
||||||
float4 color;
|
|
||||||
float2 tex_coord;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//-------------------------------------------------------------------
|
//-------------------------------------------------------------------
|
||||||
@ -103,15 +58,41 @@ float4 contrasted_color(float min, float4 fg, float4 bg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//-------------------------------------------------------------------
|
//-------------------------------------------------------------------
|
||||||
// Terminal Grid Cell Shader
|
// Cell Background Shader
|
||||||
//-------------------------------------------------------------------
|
//-------------------------------------------------------------------
|
||||||
#pragma mark - Terminal Grid Cell Shader
|
#pragma mark - Cell BG Shader
|
||||||
|
|
||||||
vertex VertexOut uber_vertex(unsigned int vid [[vertex_id]],
|
// The possible modes that a cell bg entry can take.
|
||||||
VertexIn input [[stage_in]],
|
enum CellBgMode : uint8_t {
|
||||||
constant Uniforms& uniforms [[buffer(1)]]) {
|
MODE_RGB = 1u,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CellBgVertexIn {
|
||||||
|
// The mode for this cell.
|
||||||
|
uint8_t mode [[attribute(0)]];
|
||||||
|
|
||||||
|
// The grid coordinates (x, y) where x < columns and y < rows
|
||||||
|
ushort2 grid_pos [[attribute(1)]];
|
||||||
|
|
||||||
|
// The color. For BG modes, this is the bg color, for FG modes this is
|
||||||
|
// the text color. For styles, this is the color of the style.
|
||||||
|
uchar4 color [[attribute(3)]];
|
||||||
|
|
||||||
|
// The width of the cell in cells (i.e. 2 for double-wide).
|
||||||
|
uint8_t cell_width [[attribute(2)]];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CellBgVertexOut {
|
||||||
|
float4 position [[position]];
|
||||||
|
float4 color;
|
||||||
|
};
|
||||||
|
|
||||||
|
vertex CellBgVertexOut cell_bg_vertex(unsigned int vid [[vertex_id]],
|
||||||
|
CellBgVertexIn input [[stage_in]],
|
||||||
|
constant Uniforms& uniforms
|
||||||
|
[[buffer(1)]]) {
|
||||||
// Convert the grid x,y into world space x, y by accounting for cell size
|
// Convert the grid x,y into world space x, y by accounting for cell size
|
||||||
float2 cell_pos = uniforms.cell_size * input.grid_pos;
|
float2 cell_pos = uniforms.cell_size * float2(input.grid_pos);
|
||||||
|
|
||||||
// Scaled cell size for the cell width
|
// Scaled cell size for the cell width
|
||||||
float2 cell_size_scaled = uniforms.cell_size;
|
float2 cell_size_scaled = uniforms.cell_size;
|
||||||
@ -131,82 +112,161 @@ vertex VertexOut uber_vertex(unsigned int vid [[vertex_id]],
|
|||||||
position.x = (vid == 0 || vid == 1) ? 1.0f : 0.0f;
|
position.x = (vid == 0 || vid == 1) ? 1.0f : 0.0f;
|
||||||
position.y = (vid == 0 || vid == 3) ? 0.0f : 1.0f;
|
position.y = (vid == 0 || vid == 3) ? 0.0f : 1.0f;
|
||||||
|
|
||||||
VertexOut out;
|
// Calculate the final position of our cell in world space.
|
||||||
|
// We have to add our cell size since our vertices are offset
|
||||||
|
// one cell up and to the left. (Do the math to verify yourself)
|
||||||
|
cell_pos = cell_pos + cell_size_scaled * position;
|
||||||
|
|
||||||
|
CellBgVertexOut out;
|
||||||
|
out.color = float4(input.color) / 255.0f;
|
||||||
|
out.position =
|
||||||
|
uniforms.projection_matrix * float4(cell_pos.x, cell_pos.y, 0.0f, 1.0f);
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment float4 cell_bg_fragment(CellBgVertexOut in [[stage_in]]) {
|
||||||
|
return in.color;
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------
|
||||||
|
// Cell Text Shader
|
||||||
|
//-------------------------------------------------------------------
|
||||||
|
#pragma mark - Cell Text Shader
|
||||||
|
|
||||||
|
// The possible modes that a cell fg entry can take.
|
||||||
|
enum CellTextMode : uint8_t {
|
||||||
|
MODE_TEXT = 1u,
|
||||||
|
MODE_TEXT_CONSTRAINED = 2u,
|
||||||
|
MODE_TEXT_COLOR = 3u,
|
||||||
|
MODE_TEXT_CURSOR = 4u,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CellTextVertexIn {
|
||||||
|
// The mode for this cell.
|
||||||
|
uint8_t mode [[attribute(0)]];
|
||||||
|
|
||||||
|
// The grid coordinates (x, y) where x < columns and y < rows
|
||||||
|
ushort2 grid_pos [[attribute(1)]];
|
||||||
|
|
||||||
|
// The width of the cell in cells (i.e. 2 for double-wide).
|
||||||
|
uint8_t cell_width [[attribute(6)]];
|
||||||
|
|
||||||
|
// The color of the rendered text glyph.
|
||||||
|
uchar4 color [[attribute(5)]];
|
||||||
|
|
||||||
|
// The background color of the cell. This is used to determine if
|
||||||
|
// we need to render the text with a different color to ensure
|
||||||
|
// contrast.
|
||||||
|
uchar4 bg_color [[attribute(7)]];
|
||||||
|
|
||||||
|
// The position of the glyph in the texture (x,y)
|
||||||
|
uint2 glyph_pos [[attribute(2)]];
|
||||||
|
|
||||||
|
// The size of the glyph in the texture (w,h)
|
||||||
|
uint2 glyph_size [[attribute(3)]];
|
||||||
|
|
||||||
|
// The left and top bearings for the glyph (x,y)
|
||||||
|
int2 glyph_offset [[attribute(4)]];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CellTextVertexOut {
|
||||||
|
float4 position [[position]];
|
||||||
|
float2 cell_size;
|
||||||
|
uint8_t mode;
|
||||||
|
float4 color;
|
||||||
|
float2 tex_coord;
|
||||||
|
};
|
||||||
|
|
||||||
|
vertex CellTextVertexOut cell_text_vertex(unsigned int vid [[vertex_id]],
|
||||||
|
CellTextVertexIn input [[stage_in]],
|
||||||
|
constant Uniforms& uniforms
|
||||||
|
[[buffer(1)]]) {
|
||||||
|
// Convert the grid x,y into world space x, y by accounting for cell size
|
||||||
|
float2 cell_pos = uniforms.cell_size * float2(input.grid_pos);
|
||||||
|
|
||||||
|
// Scaled cell size for the cell width
|
||||||
|
float2 cell_size_scaled = uniforms.cell_size;
|
||||||
|
cell_size_scaled.x = cell_size_scaled.x * input.cell_width;
|
||||||
|
|
||||||
|
// Turn the cell position into a vertex point depending on the
|
||||||
|
// vertex ID. Since we use instanced drawing, we have 4 vertices
|
||||||
|
// for each corner of the cell. We can use vertex ID to determine
|
||||||
|
// which one we're looking at. Using this, we can use 1 or 0 to keep
|
||||||
|
// or discard the value for the vertex.
|
||||||
|
//
|
||||||
|
// 0 = top-right
|
||||||
|
// 1 = bot-right
|
||||||
|
// 2 = bot-left
|
||||||
|
// 3 = top-left
|
||||||
|
float2 position;
|
||||||
|
position.x = (vid == 0 || vid == 1) ? 1.0f : 0.0f;
|
||||||
|
position.y = (vid == 0 || vid == 3) ? 0.0f : 1.0f;
|
||||||
|
|
||||||
|
CellTextVertexOut out;
|
||||||
out.mode = input.mode;
|
out.mode = input.mode;
|
||||||
out.cell_size = uniforms.cell_size;
|
out.cell_size = uniforms.cell_size;
|
||||||
out.color = float4(input.color) / 255.0f;
|
out.color = float4(input.color) / 255.0f;
|
||||||
switch (input.mode) {
|
|
||||||
case MODE_BG:
|
|
||||||
// Calculate the final position of our cell in world space.
|
|
||||||
// We have to add our cell size since our vertices are offset
|
|
||||||
// one cell up and to the left. (Do the math to verify yourself)
|
|
||||||
cell_pos = cell_pos + cell_size_scaled * position;
|
|
||||||
|
|
||||||
out.position = uniforms.projection_matrix *
|
float2 glyph_size = float2(input.glyph_size);
|
||||||
float4(cell_pos.x, cell_pos.y, 0.0f, 1.0f);
|
float2 glyph_offset = float2(input.glyph_offset);
|
||||||
break;
|
|
||||||
|
|
||||||
case MODE_FG:
|
// The glyph_offset.y is the y bearing, a y value that when added
|
||||||
case MODE_FG_CONSTRAINED:
|
// to the baseline is the offset (+y is up). Our grid goes down.
|
||||||
case MODE_FG_COLOR: {
|
// So we flip it with `cell_size.y - glyph_offset.y`.
|
||||||
float2 glyph_size = float2(input.glyph_size);
|
glyph_offset.y = cell_size_scaled.y - glyph_offset.y;
|
||||||
float2 glyph_offset = float2(input.glyph_offset);
|
|
||||||
|
|
||||||
// The glyph_offset.y is the y bearing, a y value that when added
|
// If we're constrained then we need to scale the glyph.
|
||||||
// to the baseline is the offset (+y is up). Our grid goes down.
|
// We also always constrain colored glyphs since we should have
|
||||||
// So we flip it with `cell_size.y - glyph_offset.y`.
|
// their scaled cell size exactly correct.
|
||||||
glyph_offset.y = cell_size_scaled.y - glyph_offset.y;
|
if (input.mode == MODE_TEXT_CONSTRAINED || input.mode == MODE_TEXT_COLOR) {
|
||||||
|
if (glyph_size.x > cell_size_scaled.x) {
|
||||||
// If we're constrained then we need to scale the glyph.
|
float new_y = glyph_size.y * (cell_size_scaled.x / glyph_size.x);
|
||||||
// We also always constrain colored glyphs since we should have
|
glyph_offset.y += (glyph_size.y - new_y) / 2;
|
||||||
// their scaled cell size exactly correct.
|
glyph_size.y = new_y;
|
||||||
if (input.mode == MODE_FG_CONSTRAINED || input.mode == MODE_FG_COLOR) {
|
glyph_size.x = cell_size_scaled.x;
|
||||||
if (glyph_size.x > cell_size_scaled.x) {
|
|
||||||
float new_y = glyph_size.y * (cell_size_scaled.x / glyph_size.x);
|
|
||||||
glyph_offset.y += (glyph_size.y - new_y) / 2;
|
|
||||||
glyph_size.y = new_y;
|
|
||||||
glyph_size.x = cell_size_scaled.x;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate the final position of the cell which uses our glyph size
|
|
||||||
// and glyph offset to create the correct bounding box for the glyph.
|
|
||||||
cell_pos = cell_pos + glyph_size * position + glyph_offset;
|
|
||||||
out.position = uniforms.projection_matrix *
|
|
||||||
float4(cell_pos.x, cell_pos.y, 0.0f, 1.0f);
|
|
||||||
|
|
||||||
// Calculate the texture coordinate in pixels. This is NOT normalized
|
|
||||||
// (between 0.0 and 1.0) and must be done in the fragment shader.
|
|
||||||
out.tex_coord =
|
|
||||||
float2(input.glyph_pos) + float2(input.glyph_size) * position;
|
|
||||||
|
|
||||||
// If we have a minimum contrast, we need to check if we need to
|
|
||||||
// change the color of the text to ensure it has enough contrast
|
|
||||||
// with the background.
|
|
||||||
if (uniforms.min_contrast > 1.0f && input.mode == MODE_FG) {
|
|
||||||
float4 bg_color = float4(input.bg_color) / 255.0f;
|
|
||||||
out.color =
|
|
||||||
contrasted_color(uniforms.min_contrast, out.color, bg_color);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate the final position of the cell which uses our glyph size
|
||||||
|
// and glyph offset to create the correct bounding box for the glyph.
|
||||||
|
cell_pos = cell_pos + glyph_size * position + glyph_offset;
|
||||||
|
out.position =
|
||||||
|
uniforms.projection_matrix * float4(cell_pos.x, cell_pos.y, 0.0f, 1.0f);
|
||||||
|
|
||||||
|
// Calculate the texture coordinate in pixels. This is NOT normalized
|
||||||
|
// (between 0.0 and 1.0) and must be done in the fragment shader.
|
||||||
|
out.tex_coord = float2(input.glyph_pos) + float2(input.glyph_size) * position;
|
||||||
|
|
||||||
|
// If we have a minimum contrast, we need to check if we need to
|
||||||
|
// change the color of the text to ensure it has enough contrast
|
||||||
|
// with the background.
|
||||||
|
if (uniforms.min_contrast > 1.0f && input.mode == MODE_TEXT) {
|
||||||
|
float4 bg_color = float4(input.bg_color) / 255.0f;
|
||||||
|
out.color = contrasted_color(uniforms.min_contrast, out.color, bg_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this cell is the cursor cell, then we need to change the color.
|
||||||
|
if (input.mode != MODE_TEXT_CURSOR &&
|
||||||
|
input.grid_pos.x == uniforms.cursor_pos.x &&
|
||||||
|
input.grid_pos.y == uniforms.cursor_pos.y) {
|
||||||
|
out.color = float4(uniforms.cursor_color) / 255.0f;
|
||||||
|
}
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
fragment float4 uber_fragment(VertexOut in [[stage_in]],
|
fragment float4 cell_text_fragment(CellTextVertexOut in [[stage_in]],
|
||||||
texture2d<float> textureGreyscale [[texture(0)]],
|
texture2d<float> textureGreyscale
|
||||||
texture2d<float> textureColor [[texture(1)]]) {
|
[[texture(0)]],
|
||||||
|
texture2d<float> textureColor
|
||||||
|
[[texture(1)]]) {
|
||||||
constexpr sampler textureSampler(address::clamp_to_edge, filter::linear);
|
constexpr sampler textureSampler(address::clamp_to_edge, filter::linear);
|
||||||
|
|
||||||
switch (in.mode) {
|
switch (in.mode) {
|
||||||
case MODE_BG:
|
case MODE_TEXT_CURSOR:
|
||||||
return in.color;
|
case MODE_TEXT_CONSTRAINED:
|
||||||
|
case MODE_TEXT: {
|
||||||
case MODE_FG_CONSTRAINED:
|
|
||||||
case MODE_FG: {
|
|
||||||
// Normalize the texture coordinates to [0,1]
|
// Normalize the texture coordinates to [0,1]
|
||||||
float2 size =
|
float2 size =
|
||||||
float2(textureGreyscale.get_width(), textureGreyscale.get_height());
|
float2(textureGreyscale.get_width(), textureGreyscale.get_height());
|
||||||
@ -222,7 +282,7 @@ fragment float4 uber_fragment(VertexOut in [[stage_in]],
|
|||||||
return premult;
|
return premult;
|
||||||
}
|
}
|
||||||
|
|
||||||
case MODE_FG_COLOR: {
|
case MODE_TEXT_COLOR: {
|
||||||
// Normalize the texture coordinates to [0,1]
|
// Normalize the texture coordinates to [0,1]
|
||||||
float2 size = float2(textureColor.get_width(), textureColor.get_height());
|
float2 size = float2(textureColor.get_width(), textureColor.get_height());
|
||||||
float2 coord = in.tex_coord / size;
|
float2 coord = in.tex_coord / size;
|
||||||
@ -230,7 +290,6 @@ fragment float4 uber_fragment(VertexOut in [[stage_in]],
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//-------------------------------------------------------------------
|
//-------------------------------------------------------------------
|
||||||
// Image Shader
|
// Image Shader
|
||||||
//-------------------------------------------------------------------
|
//-------------------------------------------------------------------
|
||||||
|
@ -1421,7 +1421,7 @@ fn resizeWithoutReflowGrowCols(
|
|||||||
|
|
||||||
// Keeps track of all our copied rows. Assertions at the end is that
|
// Keeps track of all our copied rows. Assertions at the end is that
|
||||||
// we copied exactly our page size.
|
// we copied exactly our page size.
|
||||||
var copied: usize = 0;
|
var copied: size.CellCountInt = 0;
|
||||||
|
|
||||||
// This function has an unfortunate side effect in that it causes memory
|
// This function has an unfortunate side effect in that it causes memory
|
||||||
// fragmentation on rows if the columns are increasing in a way that
|
// fragmentation on rows if the columns are increasing in a way that
|
||||||
@ -2545,7 +2545,7 @@ pub fn cellIterator(
|
|||||||
pub const RowIterator = struct {
|
pub const RowIterator = struct {
|
||||||
page_it: PageIterator,
|
page_it: PageIterator,
|
||||||
chunk: ?PageIterator.Chunk = null,
|
chunk: ?PageIterator.Chunk = null,
|
||||||
offset: usize = 0,
|
offset: size.CellCountInt = 0,
|
||||||
|
|
||||||
pub fn next(self: *RowIterator) ?Pin {
|
pub fn next(self: *RowIterator) ?Pin {
|
||||||
const chunk = self.chunk orelse return null;
|
const chunk = self.chunk orelse return null;
|
||||||
@ -2767,8 +2767,8 @@ pub const PageIterator = struct {
|
|||||||
|
|
||||||
pub const Chunk = struct {
|
pub const Chunk = struct {
|
||||||
page: *List.Node,
|
page: *List.Node,
|
||||||
start: usize,
|
start: size.CellCountInt,
|
||||||
end: usize,
|
end: size.CellCountInt,
|
||||||
|
|
||||||
pub fn rows(self: Chunk) []Row {
|
pub fn rows(self: Chunk) []Row {
|
||||||
const rows_ptr = self.page.data.rows.ptr(self.page.data.memory);
|
const rows_ptr = self.page.data.rows.ptr(self.page.data.memory);
|
||||||
@ -2944,8 +2944,8 @@ fn growRows(self: *PageList, n: usize) !void {
|
|||||||
/// should limit the number of active pins as much as possible.
|
/// should limit the number of active pins as much as possible.
|
||||||
pub const Pin = struct {
|
pub const Pin = struct {
|
||||||
page: *List.Node,
|
page: *List.Node,
|
||||||
y: usize = 0,
|
y: size.CellCountInt = 0,
|
||||||
x: usize = 0,
|
x: size.CellCountInt = 0,
|
||||||
|
|
||||||
pub fn rowAndCell(self: Pin) struct {
|
pub fn rowAndCell(self: Pin) struct {
|
||||||
row: *pagepkg.Row,
|
row: *pagepkg.Row,
|
||||||
@ -3104,7 +3104,7 @@ pub const Pin = struct {
|
|||||||
pub fn left(self: Pin, n: usize) Pin {
|
pub fn left(self: Pin, n: usize) Pin {
|
||||||
assert(n <= self.x);
|
assert(n <= self.x);
|
||||||
var result = self;
|
var result = self;
|
||||||
result.x -= n;
|
result.x -= std.math.cast(size.CellCountInt, n) orelse result.x;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3112,7 +3112,8 @@ pub const Pin = struct {
|
|||||||
pub fn right(self: Pin, n: usize) Pin {
|
pub fn right(self: Pin, n: usize) Pin {
|
||||||
assert(self.x + n < self.page.data.size.cols);
|
assert(self.x + n < self.page.data.size.cols);
|
||||||
var result = self;
|
var result = self;
|
||||||
result.x += n;
|
result.x +|= std.math.cast(size.CellCountInt, n) orelse
|
||||||
|
std.math.maxInt(size.CellCountInt);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3147,7 +3148,8 @@ pub const Pin = struct {
|
|||||||
const rows = self.page.data.size.rows - (self.y + 1);
|
const rows = self.page.data.size.rows - (self.y + 1);
|
||||||
if (n <= rows) return .{ .offset = .{
|
if (n <= rows) return .{ .offset = .{
|
||||||
.page = self.page,
|
.page = self.page,
|
||||||
.y = n + self.y,
|
.y = std.math.cast(size.CellCountInt, self.y + n) orelse
|
||||||
|
std.math.maxInt(size.CellCountInt),
|
||||||
.x = self.x,
|
.x = self.x,
|
||||||
} };
|
} };
|
||||||
|
|
||||||
@ -3165,7 +3167,8 @@ pub const Pin = struct {
|
|||||||
} };
|
} };
|
||||||
if (n_left <= page.data.size.rows) return .{ .offset = .{
|
if (n_left <= page.data.size.rows) return .{ .offset = .{
|
||||||
.page = page,
|
.page = page,
|
||||||
.y = n_left - 1,
|
.y = std.math.cast(size.CellCountInt, n_left - 1) orelse
|
||||||
|
std.math.maxInt(size.CellCountInt),
|
||||||
.x = self.x,
|
.x = self.x,
|
||||||
} };
|
} };
|
||||||
n_left -= page.data.size.rows;
|
n_left -= page.data.size.rows;
|
||||||
@ -3184,7 +3187,8 @@ pub const Pin = struct {
|
|||||||
// Index fits within this page
|
// Index fits within this page
|
||||||
if (n <= self.y) return .{ .offset = .{
|
if (n <= self.y) return .{ .offset = .{
|
||||||
.page = self.page,
|
.page = self.page,
|
||||||
.y = self.y - n,
|
.y = std.math.cast(size.CellCountInt, self.y - n) orelse
|
||||||
|
std.math.maxInt(size.CellCountInt),
|
||||||
.x = self.x,
|
.x = self.x,
|
||||||
} };
|
} };
|
||||||
|
|
||||||
@ -3198,7 +3202,8 @@ pub const Pin = struct {
|
|||||||
} };
|
} };
|
||||||
if (n_left <= page.data.size.rows) return .{ .offset = .{
|
if (n_left <= page.data.size.rows) return .{ .offset = .{
|
||||||
.page = page,
|
.page = page,
|
||||||
.y = page.data.size.rows - n_left,
|
.y = std.math.cast(size.CellCountInt, page.data.size.rows - n_left) orelse
|
||||||
|
std.math.maxInt(size.CellCountInt),
|
||||||
.x = self.x,
|
.x = self.x,
|
||||||
} };
|
} };
|
||||||
n_left -= page.data.size.rows;
|
n_left -= page.data.size.rows;
|
||||||
@ -3210,8 +3215,8 @@ const Cell = struct {
|
|||||||
page: *List.Node,
|
page: *List.Node,
|
||||||
row: *pagepkg.Row,
|
row: *pagepkg.Row,
|
||||||
cell: *pagepkg.Cell,
|
cell: *pagepkg.Cell,
|
||||||
row_idx: usize,
|
row_idx: size.CellCountInt,
|
||||||
col_idx: usize,
|
col_idx: size.CellCountInt,
|
||||||
|
|
||||||
/// Get the cell style.
|
/// Get the cell style.
|
||||||
///
|
///
|
||||||
@ -3231,7 +3236,7 @@ const Cell = struct {
|
|||||||
/// this file then consider a different approach and ask yourself very
|
/// this file then consider a different approach and ask yourself very
|
||||||
/// carefully if you really need this.
|
/// carefully if you really need this.
|
||||||
pub fn screenPoint(self: Cell) point.Point {
|
pub fn screenPoint(self: Cell) point.Point {
|
||||||
var y: usize = self.row_idx;
|
var y: size.CellCountInt = self.row_idx;
|
||||||
var page = self.page;
|
var page = self.page;
|
||||||
while (page.prev) |prev| {
|
while (page.prev) |prev| {
|
||||||
y += prev.data.size.rows;
|
y += prev.data.size.rows;
|
||||||
@ -3402,7 +3407,7 @@ test "PageList pointFromPin traverse pages" {
|
|||||||
|
|
||||||
try testing.expectEqual(point.Point{
|
try testing.expectEqual(point.Point{
|
||||||
.screen = .{
|
.screen = .{
|
||||||
.y = expected_y,
|
.y = @intCast(expected_y),
|
||||||
.x = 2,
|
.x = 2,
|
||||||
},
|
},
|
||||||
}, s.pointFromPin(.screen, .{
|
}, s.pointFromPin(.screen, .{
|
||||||
@ -5629,7 +5634,7 @@ test "PageList resize (no reflow) more rows adds blank rows if cursor at bottom"
|
|||||||
|
|
||||||
// Go through our active, we should get only 3,4,5
|
// Go through our active, we should get only 3,4,5
|
||||||
for (0..3) |y| {
|
for (0..3) |y| {
|
||||||
const get = s.getCell(.{ .active = .{ .y = y } }).?;
|
const get = s.getCell(.{ .active = .{ .y = @intCast(y) } }).?;
|
||||||
const expected: u21 = @intCast(y + 2);
|
const expected: u21 = @intCast(y + 2);
|
||||||
try testing.expectEqual(expected, get.cell.content.codepoint);
|
try testing.expectEqual(expected, get.cell.content.codepoint);
|
||||||
}
|
}
|
||||||
@ -6557,7 +6562,7 @@ test "PageList resize reflow less cols no wrapped rows" {
|
|||||||
while (it.next()) |offset| {
|
while (it.next()) |offset| {
|
||||||
for (0..4) |x| {
|
for (0..4) |x| {
|
||||||
var offset_copy = offset;
|
var offset_copy = offset;
|
||||||
offset_copy.x = x;
|
offset_copy.x = @intCast(x);
|
||||||
const rac = offset_copy.rowAndCell();
|
const rac = offset_copy.rowAndCell();
|
||||||
const cells = offset.page.data.getCells(rac.row);
|
const cells = offset.page.data.getCells(rac.row);
|
||||||
try testing.expectEqual(@as(usize, 5), cells.len);
|
try testing.expectEqual(@as(usize, 5), cells.len);
|
||||||
@ -7247,7 +7252,7 @@ test "PageList resize reflow less cols copy style" {
|
|||||||
while (it.next()) |offset| {
|
while (it.next()) |offset| {
|
||||||
for (0..s.cols - 1) |x| {
|
for (0..s.cols - 1) |x| {
|
||||||
var offset_copy = offset;
|
var offset_copy = offset;
|
||||||
offset_copy.x = x;
|
offset_copy.x = @intCast(x);
|
||||||
const rac = offset_copy.rowAndCell();
|
const rac = offset_copy.rowAndCell();
|
||||||
const style_id = rac.cell.style_id;
|
const style_id = rac.cell.style_id;
|
||||||
try testing.expect(style_id != 0);
|
try testing.expect(style_id != 0);
|
||||||
|
@ -1412,8 +1412,8 @@ pub fn selectionString(self: *Screen, alloc: Allocator, opts: SelectionString) !
|
|||||||
if (mapbuilder) |*b| {
|
if (mapbuilder) |*b| {
|
||||||
for (0..encode_len) |_| try b.append(.{
|
for (0..encode_len) |_| try b.append(.{
|
||||||
.page = chunk.page,
|
.page = chunk.page,
|
||||||
.y = y,
|
.y = @intCast(y),
|
||||||
.x = x,
|
.x = @intCast(x),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1425,8 +1425,8 @@ pub fn selectionString(self: *Screen, alloc: Allocator, opts: SelectionString) !
|
|||||||
if (mapbuilder) |*b| {
|
if (mapbuilder) |*b| {
|
||||||
for (0..encode_len) |_| try b.append(.{
|
for (0..encode_len) |_| try b.append(.{
|
||||||
.page = chunk.page,
|
.page = chunk.page,
|
||||||
.y = y,
|
.y = @intCast(y),
|
||||||
.x = x,
|
.x = @intCast(x),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1441,7 +1441,7 @@ pub fn selectionString(self: *Screen, alloc: Allocator, opts: SelectionString) !
|
|||||||
try strbuilder.append('\n');
|
try strbuilder.append('\n');
|
||||||
if (mapbuilder) |*b| try b.append(.{
|
if (mapbuilder) |*b| try b.append(.{
|
||||||
.page = chunk.page,
|
.page = chunk.page,
|
||||||
.y = y,
|
.y = @intCast(y),
|
||||||
.x = chunk.page.data.size.cols - 1,
|
.x = chunk.page.data.size.cols - 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -3959,7 +3959,10 @@ test "Screen: resize (no reflow) less rows trims blank lines" {
|
|||||||
|
|
||||||
// Write only a background color into the remaining rows
|
// Write only a background color into the remaining rows
|
||||||
for (1..s.pages.rows) |y| {
|
for (1..s.pages.rows) |y| {
|
||||||
const list_cell = s.pages.getCell(.{ .active = .{ .x = 0, .y = y } }).?;
|
const list_cell = s.pages.getCell(.{ .active = .{
|
||||||
|
.x = 0,
|
||||||
|
.y = @intCast(y),
|
||||||
|
} }).?;
|
||||||
list_cell.cell.* = .{
|
list_cell.cell.* = .{
|
||||||
.content_tag = .bg_color_rgb,
|
.content_tag = .bg_color_rgb,
|
||||||
.content = .{ .color_rgb = .{ .r = 0xFF, .g = 0, .b = 0 } },
|
.content = .{ .color_rgb = .{ .r = 0xFF, .g = 0, .b = 0 } },
|
||||||
@ -3991,7 +3994,10 @@ test "Screen: resize (no reflow) more rows trims blank lines" {
|
|||||||
|
|
||||||
// Write only a background color into the remaining rows
|
// Write only a background color into the remaining rows
|
||||||
for (1..s.pages.rows) |y| {
|
for (1..s.pages.rows) |y| {
|
||||||
const list_cell = s.pages.getCell(.{ .active = .{ .x = 0, .y = y } }).?;
|
const list_cell = s.pages.getCell(.{ .active = .{
|
||||||
|
.x = 0,
|
||||||
|
.y = @intCast(y),
|
||||||
|
} }).?;
|
||||||
list_cell.cell.* = .{
|
list_cell.cell.* = .{
|
||||||
.content_tag = .bg_color_rgb,
|
.content_tag = .bg_color_rgb,
|
||||||
.content = .{ .color_rgb = .{ .r = 0xFF, .g = 0, .b = 0 } },
|
.content = .{ .color_rgb = .{ .r = 0xFF, .g = 0, .b = 0 } },
|
||||||
@ -4118,7 +4124,10 @@ test "Screen: resize (no reflow) more rows with soft wrapping" {
|
|||||||
|
|
||||||
// Every second row should be wrapped
|
// Every second row should be wrapped
|
||||||
for (0..6) |y| {
|
for (0..6) |y| {
|
||||||
const list_cell = s.pages.getCell(.{ .screen = .{ .x = 0, .y = y } }).?;
|
const list_cell = s.pages.getCell(.{ .screen = .{
|
||||||
|
.x = 0,
|
||||||
|
.y = @intCast(y),
|
||||||
|
} }).?;
|
||||||
const row = list_cell.row;
|
const row = list_cell.row;
|
||||||
const wrapped = (y % 2 == 0);
|
const wrapped = (y % 2 == 0);
|
||||||
try testing.expectEqual(wrapped, row.wrap);
|
try testing.expectEqual(wrapped, row.wrap);
|
||||||
@ -4135,7 +4144,10 @@ test "Screen: resize (no reflow) more rows with soft wrapping" {
|
|||||||
|
|
||||||
// Every second row should be wrapped
|
// Every second row should be wrapped
|
||||||
for (0..6) |y| {
|
for (0..6) |y| {
|
||||||
const list_cell = s.pages.getCell(.{ .screen = .{ .x = 0, .y = y } }).?;
|
const list_cell = s.pages.getCell(.{ .screen = .{
|
||||||
|
.x = 0,
|
||||||
|
.y = @intCast(y),
|
||||||
|
} }).?;
|
||||||
const row = list_cell.row;
|
const row = list_cell.row;
|
||||||
const wrapped = (y % 2 == 0);
|
const wrapped = (y % 2 == 0);
|
||||||
try testing.expectEqual(wrapped, row.wrap);
|
try testing.expectEqual(wrapped, row.wrap);
|
||||||
|
@ -435,7 +435,7 @@ pub fn adjust(
|
|||||||
const cells = next.page.data.getCells(rac.row);
|
const cells = next.page.data.getCells(rac.row);
|
||||||
if (page.Cell.hasTextAny(cells)) {
|
if (page.Cell.hasTextAny(cells)) {
|
||||||
end_pin.* = next;
|
end_pin.* = next;
|
||||||
end_pin.x = cells.len - 1;
|
end_pin.x = @intCast(cells.len - 1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4169,7 +4169,10 @@ test "Terminal: insertLines colors with bg color" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (0..t.cols) |x| {
|
for (0..t.cols) |x| {
|
||||||
const list_cell = t.screen.pages.getCell(.{ .active = .{ .x = x, .y = 1 } }).?;
|
const list_cell = t.screen.pages.getCell(.{ .active = .{
|
||||||
|
.x = @intCast(x),
|
||||||
|
.y = 1,
|
||||||
|
} }).?;
|
||||||
try testing.expect(list_cell.cell.content_tag == .bg_color_rgb);
|
try testing.expect(list_cell.cell.content_tag == .bg_color_rgb);
|
||||||
try testing.expectEqual(Cell.RGB{
|
try testing.expectEqual(Cell.RGB{
|
||||||
.r = 0xFF,
|
.r = 0xFF,
|
||||||
@ -5297,7 +5300,10 @@ test "Terminal: index bottom of primary screen background sgr" {
|
|||||||
defer testing.allocator.free(str);
|
defer testing.allocator.free(str);
|
||||||
try testing.expectEqualStrings("\n\n\nA", str);
|
try testing.expectEqualStrings("\n\n\nA", str);
|
||||||
for (0..5) |x| {
|
for (0..5) |x| {
|
||||||
const list_cell = t.screen.pages.getCell(.{ .active = .{ .x = x, .y = 4 } }).?;
|
const list_cell = t.screen.pages.getCell(.{ .active = .{
|
||||||
|
.x = @intCast(x),
|
||||||
|
.y = 4,
|
||||||
|
} }).?;
|
||||||
try testing.expect(list_cell.cell.content_tag == .bg_color_rgb);
|
try testing.expect(list_cell.cell.content_tag == .bg_color_rgb);
|
||||||
try testing.expectEqual(Cell.RGB{
|
try testing.expectEqual(Cell.RGB{
|
||||||
.r = 0xFF,
|
.r = 0xFF,
|
||||||
@ -5349,7 +5355,10 @@ test "Terminal: index bottom of scroll region with background SGR" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (0..t.cols) |x| {
|
for (0..t.cols) |x| {
|
||||||
const list_cell = t.screen.pages.getCell(.{ .active = .{ .x = x, .y = 2 } }).?;
|
const list_cell = t.screen.pages.getCell(.{ .active = .{
|
||||||
|
.x = @intCast(x),
|
||||||
|
.y = 2,
|
||||||
|
} }).?;
|
||||||
try testing.expect(list_cell.cell.content_tag == .bg_color_rgb);
|
try testing.expect(list_cell.cell.content_tag == .bg_color_rgb);
|
||||||
try testing.expectEqual(Cell.RGB{
|
try testing.expectEqual(Cell.RGB{
|
||||||
.r = 0xFF,
|
.r = 0xFF,
|
||||||
@ -5961,7 +5970,10 @@ test "Terminal: deleteLines colors with bg color" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (0..t.cols) |x| {
|
for (0..t.cols) |x| {
|
||||||
const list_cell = t.screen.pages.getCell(.{ .active = .{ .x = x, .y = 4 } }).?;
|
const list_cell = t.screen.pages.getCell(.{ .active = .{
|
||||||
|
.x = @intCast(x),
|
||||||
|
.y = 4,
|
||||||
|
} }).?;
|
||||||
try testing.expect(list_cell.cell.content_tag == .bg_color_rgb);
|
try testing.expect(list_cell.cell.content_tag == .bg_color_rgb);
|
||||||
try testing.expectEqual(Cell.RGB{
|
try testing.expectEqual(Cell.RGB{
|
||||||
.r = 0xFF,
|
.r = 0xFF,
|
||||||
@ -6148,7 +6160,10 @@ test "Terminal: deleteLines resets wrap" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (0..t.rows) |y| {
|
for (0..t.rows) |y| {
|
||||||
const list_cell = t.screen.pages.getCell(.{ .active = .{ .x = 0, .y = y } }).?;
|
const list_cell = t.screen.pages.getCell(.{ .active = .{
|
||||||
|
.x = 0,
|
||||||
|
.y = @intCast(y),
|
||||||
|
} }).?;
|
||||||
const row = list_cell.row;
|
const row = list_cell.row;
|
||||||
try testing.expect(!row.wrap);
|
try testing.expect(!row.wrap);
|
||||||
}
|
}
|
||||||
@ -7183,7 +7198,10 @@ test "Terminal: deleteChars preserves background sgr" {
|
|||||||
try testing.expectEqualStrings("AB23", str);
|
try testing.expectEqualStrings("AB23", str);
|
||||||
}
|
}
|
||||||
for (t.cols - 2..t.cols) |x| {
|
for (t.cols - 2..t.cols) |x| {
|
||||||
const list_cell = t.screen.pages.getCell(.{ .active = .{ .x = x, .y = 0 } }).?;
|
const list_cell = t.screen.pages.getCell(.{ .active = .{
|
||||||
|
.x = @intCast(x),
|
||||||
|
.y = 0,
|
||||||
|
} }).?;
|
||||||
try testing.expect(list_cell.cell.content_tag == .bg_color_rgb);
|
try testing.expect(list_cell.cell.content_tag == .bg_color_rgb);
|
||||||
try testing.expectEqual(Cell.RGB{
|
try testing.expectEqual(Cell.RGB{
|
||||||
.r = 0xFF,
|
.r = 0xFF,
|
||||||
@ -7573,7 +7591,10 @@ test "Terminal: eraseLine right preserves background sgr" {
|
|||||||
defer testing.allocator.free(str);
|
defer testing.allocator.free(str);
|
||||||
try testing.expectEqualStrings("A", str);
|
try testing.expectEqualStrings("A", str);
|
||||||
for (1..5) |x| {
|
for (1..5) |x| {
|
||||||
const list_cell = t.screen.pages.getCell(.{ .active = .{ .x = x, .y = 0 } }).?;
|
const list_cell = t.screen.pages.getCell(.{ .active = .{
|
||||||
|
.x = @intCast(x),
|
||||||
|
.y = 0,
|
||||||
|
} }).?;
|
||||||
try testing.expect(list_cell.cell.content_tag == .bg_color_rgb);
|
try testing.expect(list_cell.cell.content_tag == .bg_color_rgb);
|
||||||
try testing.expectEqual(Cell.RGB{
|
try testing.expectEqual(Cell.RGB{
|
||||||
.r = 0xFF,
|
.r = 0xFF,
|
||||||
@ -7727,7 +7748,10 @@ test "Terminal: eraseLine left preserves background sgr" {
|
|||||||
defer testing.allocator.free(str);
|
defer testing.allocator.free(str);
|
||||||
try testing.expectEqualStrings(" CDE", str);
|
try testing.expectEqualStrings(" CDE", str);
|
||||||
for (0..2) |x| {
|
for (0..2) |x| {
|
||||||
const list_cell = t.screen.pages.getCell(.{ .active = .{ .x = x, .y = 0 } }).?;
|
const list_cell = t.screen.pages.getCell(.{ .active = .{
|
||||||
|
.x = @intCast(x),
|
||||||
|
.y = 0,
|
||||||
|
} }).?;
|
||||||
try testing.expect(list_cell.cell.content_tag == .bg_color_rgb);
|
try testing.expect(list_cell.cell.content_tag == .bg_color_rgb);
|
||||||
try testing.expectEqual(Cell.RGB{
|
try testing.expectEqual(Cell.RGB{
|
||||||
.r = 0xFF,
|
.r = 0xFF,
|
||||||
@ -7847,7 +7871,10 @@ test "Terminal: eraseLine complete preserves background sgr" {
|
|||||||
defer testing.allocator.free(str);
|
defer testing.allocator.free(str);
|
||||||
try testing.expectEqualStrings("", str);
|
try testing.expectEqualStrings("", str);
|
||||||
for (0..5) |x| {
|
for (0..5) |x| {
|
||||||
const list_cell = t.screen.pages.getCell(.{ .active = .{ .x = x, .y = 0 } }).?;
|
const list_cell = t.screen.pages.getCell(.{ .active = .{
|
||||||
|
.x = @intCast(x),
|
||||||
|
.y = 0,
|
||||||
|
} }).?;
|
||||||
try testing.expect(list_cell.cell.content_tag == .bg_color_rgb);
|
try testing.expect(list_cell.cell.content_tag == .bg_color_rgb);
|
||||||
try testing.expectEqual(Cell.RGB{
|
try testing.expectEqual(Cell.RGB{
|
||||||
.r = 0xFF,
|
.r = 0xFF,
|
||||||
@ -8096,7 +8123,10 @@ test "Terminal: eraseDisplay erase below preserves SGR bg" {
|
|||||||
defer testing.allocator.free(str);
|
defer testing.allocator.free(str);
|
||||||
try testing.expectEqualStrings("ABC\nD", str);
|
try testing.expectEqualStrings("ABC\nD", str);
|
||||||
for (1..5) |x| {
|
for (1..5) |x| {
|
||||||
const list_cell = t.screen.pages.getCell(.{ .active = .{ .x = x, .y = 1 } }).?;
|
const list_cell = t.screen.pages.getCell(.{ .active = .{
|
||||||
|
.x = @intCast(x),
|
||||||
|
.y = 1,
|
||||||
|
} }).?;
|
||||||
try testing.expect(list_cell.cell.content_tag == .bg_color_rgb);
|
try testing.expect(list_cell.cell.content_tag == .bg_color_rgb);
|
||||||
try testing.expectEqual(Cell.RGB{
|
try testing.expectEqual(Cell.RGB{
|
||||||
.r = 0xFF,
|
.r = 0xFF,
|
||||||
@ -8271,7 +8301,10 @@ test "Terminal: eraseDisplay erase above preserves SGR bg" {
|
|||||||
defer testing.allocator.free(str);
|
defer testing.allocator.free(str);
|
||||||
try testing.expectEqualStrings("\n F\nGHI", str);
|
try testing.expectEqualStrings("\n F\nGHI", str);
|
||||||
for (0..2) |x| {
|
for (0..2) |x| {
|
||||||
const list_cell = t.screen.pages.getCell(.{ .active = .{ .x = x, .y = 1 } }).?;
|
const list_cell = t.screen.pages.getCell(.{ .active = .{
|
||||||
|
.x = @intCast(x),
|
||||||
|
.y = 1,
|
||||||
|
} }).?;
|
||||||
try testing.expect(list_cell.cell.content_tag == .bg_color_rgb);
|
try testing.expect(list_cell.cell.content_tag == .bg_color_rgb);
|
||||||
try testing.expectEqual(Cell.RGB{
|
try testing.expectEqual(Cell.RGB{
|
||||||
.r = 0xFF,
|
.r = 0xFF,
|
||||||
|
@ -5,6 +5,7 @@ const ArenaAllocator = std.heap.ArenaAllocator;
|
|||||||
|
|
||||||
const terminal = @import("../main.zig");
|
const terminal = @import("../main.zig");
|
||||||
const point = @import("../point.zig");
|
const point = @import("../point.zig");
|
||||||
|
const size = @import("../size.zig");
|
||||||
const command = @import("graphics_command.zig");
|
const command = @import("graphics_command.zig");
|
||||||
const PageList = @import("../PageList.zig");
|
const PageList = @import("../PageList.zig");
|
||||||
const Screen = @import("../Screen.zig");
|
const Screen = @import("../Screen.zig");
|
||||||
@ -265,13 +266,13 @@ pub const ImageStorage = struct {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
.intersect_cell => |v| {
|
.intersect_cell => |v| intersect_cell: {
|
||||||
self.deleteIntersecting(
|
self.deleteIntersecting(
|
||||||
alloc,
|
alloc,
|
||||||
t,
|
t,
|
||||||
.{ .active = .{
|
.{ .active = .{
|
||||||
.x = v.x,
|
.x = std.math.cast(size.CellCountInt, v.x) orelse break :intersect_cell,
|
||||||
.y = v.y,
|
.y = std.math.cast(size.CellCountInt, v.y) orelse break :intersect_cell,
|
||||||
} },
|
} },
|
||||||
v.delete,
|
v.delete,
|
||||||
{},
|
{},
|
||||||
@ -279,13 +280,13 @@ pub const ImageStorage = struct {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
.intersect_cell_z => |v| {
|
.intersect_cell_z => |v| intersect_cell_z: {
|
||||||
self.deleteIntersecting(
|
self.deleteIntersecting(
|
||||||
alloc,
|
alloc,
|
||||||
t,
|
t,
|
||||||
.{ .active = .{
|
.{ .active = .{
|
||||||
.x = v.x,
|
.x = std.math.cast(size.CellCountInt, v.x) orelse break :intersect_cell_z,
|
||||||
.y = v.y,
|
.y = std.math.cast(size.CellCountInt, v.y) orelse break :intersect_cell_z,
|
||||||
} },
|
} },
|
||||||
v.delete,
|
v.delete,
|
||||||
v.z,
|
v.z,
|
||||||
@ -317,7 +318,7 @@ pub const ImageStorage = struct {
|
|||||||
// v.y is in active coords so we want to convert it to a pin
|
// v.y is in active coords so we want to convert it to a pin
|
||||||
// so we can compare by page offsets.
|
// so we can compare by page offsets.
|
||||||
const target_pin = t.screen.pages.pin(.{ .active = .{
|
const target_pin = t.screen.pages.pin(.{ .active = .{
|
||||||
.y = v.y,
|
.y = std.math.cast(size.CellCountInt, v.y) orelse break :row,
|
||||||
} }) orelse break :row;
|
} }) orelse break :row;
|
||||||
|
|
||||||
var it = self.placements.iterator();
|
var it = self.placements.iterator();
|
||||||
|
@ -25,6 +25,7 @@ pub const Charset = charsets.Charset;
|
|||||||
pub const CharsetSlot = charsets.Slots;
|
pub const CharsetSlot = charsets.Slots;
|
||||||
pub const CharsetActiveSlot = charsets.ActiveSlot;
|
pub const CharsetActiveSlot = charsets.ActiveSlot;
|
||||||
pub const Cell = page.Cell;
|
pub const Cell = page.Cell;
|
||||||
|
pub const Coordinate = point.Coordinate;
|
||||||
pub const CSI = Parser.Action.CSI;
|
pub const CSI = Parser.Action.CSI;
|
||||||
pub const DCS = Parser.Action.DCS;
|
pub const DCS = Parser.Action.DCS;
|
||||||
pub const MouseShape = @import("mouse_shape.zig").MouseShape;
|
pub const MouseShape = @import("mouse_shape.zig").MouseShape;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
|
const size = @import("size.zig");
|
||||||
|
|
||||||
/// The possible reference locations for a point. When someone says "(42, 80)" in the context of a terminal, that could mean multiple
|
/// The possible reference locations for a point. When someone says "(42, 80)" in the context of a terminal, that could mean multiple
|
||||||
/// things: it is in the current visible viewport? the current active
|
/// things: it is in the current visible viewport? the current active
|
||||||
@ -65,8 +66,8 @@ pub const Point = union(Tag) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const Coordinate = struct {
|
pub const Coordinate = struct {
|
||||||
x: usize = 0,
|
x: size.CellCountInt = 0,
|
||||||
y: usize = 0,
|
y: size.CellCountInt = 0,
|
||||||
|
|
||||||
pub fn eql(self: Coordinate, other: Coordinate) bool {
|
pub fn eql(self: Coordinate, other: Coordinate) bool {
|
||||||
return self.x == other.x and self.y == other.y;
|
return self.x == other.x and self.y == other.y;
|
||||||
|
Reference in New Issue
Block a user