mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-17 01:06:08 +03:00
Merge pull request #1001 from vancluever/vancluever-powerline-half-circles
Powerline: Add half-circle rendering
This commit is contained in:
@ -148,6 +148,8 @@ const Kind = enum {
|
|||||||
|
|
||||||
// Powerline fonts
|
// Powerline fonts
|
||||||
0xE0B0,
|
0xE0B0,
|
||||||
|
0xE0B4,
|
||||||
|
0xE0B6,
|
||||||
0xE0B2,
|
0xE0B2,
|
||||||
0xE0B8,
|
0xE0B8,
|
||||||
0xE0BA,
|
0xE0BA,
|
||||||
|
@ -56,7 +56,7 @@ pub fn renderGlyph(
|
|||||||
defer canvas.deinit(alloc);
|
defer canvas.deinit(alloc);
|
||||||
|
|
||||||
// Perform the actual drawing
|
// Perform the actual drawing
|
||||||
try self.draw(&canvas, cp);
|
try self.draw(alloc, &canvas, cp);
|
||||||
|
|
||||||
// Write the drawing to the atlas
|
// Write the drawing to the atlas
|
||||||
const region = try canvas.writeAtlas(alloc, atlas);
|
const region = try canvas.writeAtlas(alloc, atlas);
|
||||||
@ -77,7 +77,7 @@ pub fn renderGlyph(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(self: Powerline, canvas: *font.sprite.Canvas, cp: u32) !void {
|
fn draw(self: Powerline, alloc: Allocator, canvas: *font.sprite.Canvas, cp: u32) !void {
|
||||||
switch (cp) {
|
switch (cp) {
|
||||||
// Hard dividers and triangles
|
// Hard dividers and triangles
|
||||||
0xE0B0,
|
0xE0B0,
|
||||||
@ -88,6 +88,11 @@ fn draw(self: Powerline, canvas: *font.sprite.Canvas, cp: u32) !void {
|
|||||||
0xE0BE,
|
0xE0BE,
|
||||||
=> try self.draw_wedge_triangle(canvas, cp),
|
=> try self.draw_wedge_triangle(canvas, cp),
|
||||||
|
|
||||||
|
// Half-circles
|
||||||
|
0xE0B4,
|
||||||
|
0xE0B6,
|
||||||
|
=> try self.draw_half_circle(alloc, canvas, cp),
|
||||||
|
|
||||||
else => return error.InvalidCodepoint,
|
else => return error.InvalidCodepoint,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -168,6 +173,156 @@ fn draw_wedge_triangle(self: Powerline, canvas: *font.sprite.Canvas, cp: u32) !v
|
|||||||
}, .on);
|
}, .on);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_half_circle(self: Powerline, alloc: Allocator, canvas: *font.sprite.Canvas, cp: u32) !void {
|
||||||
|
const supersample = 4;
|
||||||
|
|
||||||
|
// We make a canvas big enough for the whole circle, with the supersample
|
||||||
|
// applied.
|
||||||
|
const width = self.width * 2 * supersample;
|
||||||
|
const height = self.height * supersample;
|
||||||
|
|
||||||
|
// We set a minimum super-sampled canvas to assert on. The minimum cell
|
||||||
|
// size is 1x3px, and this looked safe in empirical testing.
|
||||||
|
std.debug.assert(width >= 8); // 1 * 2 * 4
|
||||||
|
std.debug.assert(height >= 12); // 3 * 4
|
||||||
|
|
||||||
|
const center_x = width / 2 - 1;
|
||||||
|
const center_y = height / 2 - 1;
|
||||||
|
|
||||||
|
// Our radius.
|
||||||
|
const radius = @min(width, height) / 2;
|
||||||
|
|
||||||
|
// Pre-allocate a matrix to plot the points on.
|
||||||
|
const cap = height * width;
|
||||||
|
var points = try alloc.alloc(u8, cap);
|
||||||
|
defer alloc.free(points);
|
||||||
|
@memset(points, 0);
|
||||||
|
{
|
||||||
|
// Using a midpoint algorithm.
|
||||||
|
// As explained on https://yurichev.com/news/20220322_circle/ and
|
||||||
|
// other sites
|
||||||
|
var x: i32 = @intCast(radius);
|
||||||
|
var y: i32 = 0;
|
||||||
|
var radius_err: i32 = 0;
|
||||||
|
|
||||||
|
const cx: i32 = @intCast(center_x);
|
||||||
|
const cy: i32 = @intCast(center_y);
|
||||||
|
|
||||||
|
while (x >= y) {
|
||||||
|
// Right side
|
||||||
|
const x1 = @max(0, cx + x);
|
||||||
|
const y1 = @max(0, cy + y);
|
||||||
|
const x2 = @max(0, cx + x);
|
||||||
|
const y2 = @max(0, cy - y);
|
||||||
|
const x3 = @max(0, cx + y);
|
||||||
|
const y3 = @max(0, cy + x);
|
||||||
|
const x4 = @max(0, cx + y);
|
||||||
|
const y4 = @max(0, cy - x);
|
||||||
|
|
||||||
|
// Left side
|
||||||
|
const x5 = @max(0, cx - x);
|
||||||
|
const y5 = @max(0, cy + y);
|
||||||
|
const x6 = @max(0, cx - x);
|
||||||
|
const y6 = @max(0, cy - y);
|
||||||
|
const x7 = @max(0, cx - y);
|
||||||
|
const y7 = @max(0, cy + x);
|
||||||
|
const x8 = @max(0, cx - y);
|
||||||
|
const y8 = @max(0, cy - x);
|
||||||
|
|
||||||
|
// Points
|
||||||
|
const p1 = y1 * width + x1;
|
||||||
|
const p2 = y2 * width + x2;
|
||||||
|
const p3 = y3 * width + x3;
|
||||||
|
const p4 = y4 * width + x4;
|
||||||
|
const p5 = y5 * width + x5;
|
||||||
|
const p6 = y6 * width + x6;
|
||||||
|
const p7 = y7 * width + x7;
|
||||||
|
const p8 = y8 * width + x8;
|
||||||
|
|
||||||
|
// Set the points in the matrix, ignore any out of bounds
|
||||||
|
if (p1 < cap) points[p1] = 0xFF;
|
||||||
|
if (p2 < cap) points[p2] = 0xFF;
|
||||||
|
if (p3 < cap) points[p3] = 0xFF;
|
||||||
|
if (p4 < cap) points[p4] = 0xFF;
|
||||||
|
if (p5 < cap) points[p5] = 0xFF;
|
||||||
|
if (p6 < cap) points[p6] = 0xFF;
|
||||||
|
if (p7 < cap) points[p7] = 0xFF;
|
||||||
|
if (p8 < cap) points[p8] = 0xFF;
|
||||||
|
|
||||||
|
// Calculate next pixels based on midpoint bounds
|
||||||
|
y += 1;
|
||||||
|
radius_err += 2 * y + 1;
|
||||||
|
if (radius_err > 0) {
|
||||||
|
x -= 1;
|
||||||
|
radius_err -= 2 * x + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill
|
||||||
|
{
|
||||||
|
const u_height: u32 = @intCast(height);
|
||||||
|
const u_width: u32 = @intCast(width);
|
||||||
|
|
||||||
|
for (0..u_height) |yf| {
|
||||||
|
for (0..u_width) |left| {
|
||||||
|
// Count forward from the left to the first filled pixel
|
||||||
|
if (points[yf * u_width + left] != 0) {
|
||||||
|
// Count back to our left point from the right to the first
|
||||||
|
// filled pixel on the other side.
|
||||||
|
var right: usize = u_width - 1;
|
||||||
|
while (right > left) : (right -= 1) {
|
||||||
|
if (points[yf * u_width + right] != 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start filling 1 index after the left and go until we hit
|
||||||
|
// the right; this will be a no-op if the line length is <
|
||||||
|
// 3 as both left and right will have already been filled.
|
||||||
|
const start = yf * u_width + left;
|
||||||
|
const end = yf * u_width + right;
|
||||||
|
if (end - start >= 3) {
|
||||||
|
for (start + 1..end) |idx| {
|
||||||
|
points[idx] = 0xFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now that we have our points, we need to "split" our matrix on the x
|
||||||
|
// axis for the downsample.
|
||||||
|
{
|
||||||
|
// The side of the circle we're drawing
|
||||||
|
const offset_j: u32 = if (cp == 0xE0B4) center_x + 1 else 0;
|
||||||
|
|
||||||
|
for (0..self.height) |r| {
|
||||||
|
for (0..self.width) |c| {
|
||||||
|
var total: u32 = 0;
|
||||||
|
for (0..supersample) |i| {
|
||||||
|
for (0..supersample) |j| {
|
||||||
|
const idx = (r * supersample + i) * width + (c * supersample + j + offset_j);
|
||||||
|
total += points[idx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const average = @as(u8, @intCast(@min(total / (supersample * supersample), 0xFF)));
|
||||||
|
canvas.rect(
|
||||||
|
.{
|
||||||
|
.x = @intCast(c),
|
||||||
|
.y = @intCast(r),
|
||||||
|
.width = 1,
|
||||||
|
.height = 1,
|
||||||
|
},
|
||||||
|
@as(font.sprite.Color, @enumFromInt(average)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test "all" {
|
test "all" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
@ -179,6 +334,8 @@ test "all" {
|
|||||||
0xE0BA,
|
0xE0BA,
|
||||||
0xE0BC,
|
0xE0BC,
|
||||||
0xE0BE,
|
0xE0BE,
|
||||||
|
0xE0B4,
|
||||||
|
0xE0B6,
|
||||||
};
|
};
|
||||||
for (cps) |cp| {
|
for (cps) |cp| {
|
||||||
var atlas_greyscale = try font.Atlas.init(alloc, 512, .greyscale);
|
var atlas_greyscale = try font.Atlas.init(alloc, 512, .greyscale);
|
||||||
|
Reference in New Issue
Block a user