Skip to content

Commit

Permalink
feat?: Add background image support for OpenGL
Browse files Browse the repository at this point in the history
  • Loading branch information
yunusey authored and pseudocc committed Jan 14, 2025
1 parent 5081e65 commit 5aa9d0c
Show file tree
Hide file tree
Showing 6 changed files with 616 additions and 74 deletions.
2 changes: 2 additions & 0 deletions src/config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ pub const RepeatableCodepointMap = Config.RepeatableCodepointMap;
pub const RepeatableFontVariation = Config.RepeatableFontVariation;
pub const RepeatableString = Config.RepeatableString;
pub const RepeatablePath = Config.RepeatablePath;
pub const SinglePath = Config.SinglePath;
pub const ShellIntegrationFeatures = Config.ShellIntegrationFeatures;
pub const WindowPaddingColor = Config.WindowPaddingColor;
pub const BackgroundImageMode = Config.BackgroundImageMode;

// Alternate APIs
pub const CAPI = @import("config/CAPI.zig");
Expand Down
259 changes: 186 additions & 73 deletions src/config/Config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const global_state = &@import("../global.zig").state;
const fontpkg = @import("../font/main.zig");
const inputpkg = @import("../input.zig");
const terminal = @import("../terminal/main.zig");
const BackgroundImageProgram = @import("../renderer/opengl/BackgroundImageProgram.zig");
const internal_os = @import("../os/main.zig");
const cli = @import("../cli.zig");
const Command = @import("../Command.zig");
Expand Down Expand Up @@ -450,6 +451,32 @@ background: Color = .{ .r = 0x28, .g = 0x2C, .b = 0x34 },
/// Specified as either hex (`#RRGGBB` or `RRGGBB`) or a named X11 color.
foreground: Color = .{ .r = 0xFF, .g = 0xFF, .b = 0xFF },

/// Background image for the window.
@"background-image": SinglePath = .{},

/// Background image opacity
@"background-image-opacity": f32 = 1.0,

/// Background image mode to use.
///
/// Valid values are:
///
/// * `zoomed` - Image is scaled to fit the window, preserving aspect ratio.
/// * `scaled` - Image is scaled to fill the window, not preserving aspect ratio.
/// * `tiled` - Image is repeated horizontally and vertically to fill the window.
/// * `centered` - Image is centered in the window and displayed 1-to-1 pixel
/// scale, preserving both the aspect ratio and the image size.
/// * `upper-left` - Image is anchored to the upper left corner of the window,
/// preserving the aspect ratio.
/// * `upper-right` - Image is anchored to the upper right corner of the window,
/// preserving the aspect ratio.
/// * `lower-left` - Image is anchored to the lower left corner of the window,
/// preserving the aspect ratio.
/// * `lower-right` - Image is anchored to the lower right corner of the window,
/// preserving the aspect ratio.
///
@"background-image-mode": BackgroundImageMode = .zoomed,

/// The foreground and background color for selection. If this is not set, then
/// the selection color is just the inverted window background and foreground
/// (note: not to be confused with the cell bg/fg).
Expand Down Expand Up @@ -3303,7 +3330,7 @@ fn expandPaths(self: *Config, base: []const u8) !void {

// Expand all of our paths
inline for (@typeInfo(Config).Struct.fields) |field| {
if (field.type == RepeatablePath) {
if (field.type == RepeatablePath or field.type == SinglePath) {
try @field(self, field.name).expand(
arena_alloc,
base,
Expand All @@ -3313,6 +3340,86 @@ fn expandPaths(self: *Config, base: []const u8) !void {
}
}

/// Expand a relative path to an absolute path. This function is used by
/// the RepeatablePath and SinglePath to expand the paths they store.
fn expandPath(
alloc: Allocator,
base: []const u8,
path: []const u8,
diags: *cli.DiagnosticList,
) ![]const u8 {
assert(std.fs.path.isAbsolute(base));
var dir = try std.fs.cwd().openDir(base, .{});
defer dir.close();

// If it is already absolute we can just return it
if (path.len == 0 or std.fs.path.isAbsolute(path)) return path;

// If it isn't absolute, we need to make it absolute relative
// to the base.
var buf: [std.fs.max_path_bytes]u8 = undefined;

// Check if the path starts with a tilde and expand it to the
// home directory on Linux/macOS. We explicitly look for "~/"
// because we don't support alternate users such as "~alice/"
if (std.mem.startsWith(u8, path, "~/")) expand: {
// Windows isn't supported yet
if (comptime builtin.os.tag == .windows) break :expand;

const expanded: []const u8 = internal_os.expandHome(
path,
&buf,
) catch |err| {
try diags.append(alloc, .{
.message = try std.fmt.allocPrintZ(
alloc,
"error expanding home directory for path {s}: {}",
.{ path, err },
),
});

// We can't expand this path so return an empty string
return "";
};

log.debug(
"expanding file path from home directory: path={s}",
.{expanded},
);

return expanded;
}

const abs = dir.realpath(path, &buf) catch |err| abs: {
if (err == error.FileNotFound) {
// The file doesn't exist. Try to resolve the relative path
// another way.
const resolved = try std.fs.path.resolve(alloc, &.{ base, path });
defer alloc.free(resolved);
@memcpy(buf[0..resolved.len], resolved);
break :abs buf[0..resolved.len];
}

try diags.append(alloc, .{
.message = try std.fmt.allocPrintZ(
alloc,
"error resolving file path {s}: {}",
.{ path, err },
),
});

// We can't expand this path so return an empty string
return "";
};

log.debug(
"expanding file path relative={s} abs={s}",
.{ path, abs },
);

return abs;
}

fn loadTheme(self: *Config, theme: Theme) !void {
// Load the correct theme depending on the conditional state.
// Dark/light themes were programmed prior to conditional configuration
Expand Down Expand Up @@ -4407,6 +4514,63 @@ pub const Palette = struct {
}
};

/// SinglePath is a path to a single file. When loading the configuration
/// file, always the last one will be kept and be automatically expanded
/// relative to the path of the config file.
pub const SinglePath = struct {
const Self = @This();

/// The actual value that is updated as we parse.
value: ?[]const u8 = null,

/// Parse a single path.
pub fn parseCLI(self: *Self, alloc: Allocator, input: ?[]const u8) !void {
const value = input orelse return error.ValueRequired;
// If the value is empty, we set the value to null
if (value.len == 0) {
self.value = null;
return;
}
const copy = try alloc.dupe(u8, value);
self.value = copy;
}

/// Deep copy of the struct. Required by Config.
pub fn clone(self: Self, alloc: Allocator) Allocator.Error!Self {
const value = self.value orelse return .{};

const copy_path = try alloc.dupe(u8, value);
return .{
.value = copy_path,
};
}

/// Used by Formatter
pub fn formatEntry(self: Self, formatter: anytype) !void {
const value = self.value orelse return;
try formatter.formatEntry([]const u8, value);
}

/// Expand all the paths relative to the base directory.
pub fn expand(
self: *Self,
alloc: Allocator,
base: []const u8,
diags: *cli.DiagnosticList,
) !void {
// Try expanding path relative to the base.
const path = self.value orelse return;
const abs = try expandPath(alloc, base, path, diags);

if (abs.len == 0) {
// Blank this path so that we don't attempt to resolve it again
self.value = null;
return;
}
self.value = try alloc.dupeZ(u8, abs);
}
};

/// RepeatableString is a string value that can be repeated to accumulate
/// a list of strings. This isn't called "StringList" because I find that
/// sometimes leads to confusion that it _accepts_ a list such as
Expand Down Expand Up @@ -4638,88 +4802,20 @@ pub const RepeatablePath = struct {
base: []const u8,
diags: *cli.DiagnosticList,
) !void {
assert(std.fs.path.isAbsolute(base));
var dir = try std.fs.cwd().openDir(base, .{});
defer dir.close();

for (0..self.value.items.len) |i| {
const path = switch (self.value.items[i]) {
.optional, .required => |path| path,
};

// If it is already absolute we can ignore it.
if (path.len == 0 or std.fs.path.isAbsolute(path)) continue;

// If it isn't absolute, we need to make it absolute relative
// to the base.
var buf: [std.fs.max_path_bytes]u8 = undefined;

// Check if the path starts with a tilde and expand it to the
// home directory on Linux/macOS. We explicitly look for "~/"
// because we don't support alternate users such as "~alice/"
if (std.mem.startsWith(u8, path, "~/")) expand: {
// Windows isn't supported yet
if (comptime builtin.os.tag == .windows) break :expand;

const expanded: []const u8 = internal_os.expandHome(
path,
&buf,
) catch |err| {
try diags.append(alloc, .{
.message = try std.fmt.allocPrintZ(
alloc,
"error expanding home directory for path {s}: {}",
.{ path, err },
),
});

// Blank this path so that we don't attempt to resolve it
// again
self.value.items[i] = .{ .required = "" };

continue;
};

log.debug(
"expanding file path from home directory: path={s}",
.{expanded},
);

switch (self.value.items[i]) {
.optional, .required => |*p| p.* = try alloc.dupeZ(u8, expanded),
}

continue;
}

const abs = dir.realpath(path, &buf) catch |err| abs: {
if (err == error.FileNotFound) {
// The file doesn't exist. Try to resolve the relative path
// another way.
const resolved = try std.fs.path.resolve(alloc, &.{ base, path });
defer alloc.free(resolved);
@memcpy(buf[0..resolved.len], resolved);
break :abs buf[0..resolved.len];
}

try diags.append(alloc, .{
.message = try std.fmt.allocPrintZ(
alloc,
"error resolving file path {s}: {}",
.{ path, err },
),
});
// Try expanding path relative to the base.
const abs = try expandPath(alloc, base, path, diags);

if (abs.len == 0) {
// Blank this path so that we don't attempt to resolve it again
self.value.items[i] = .{ .required = "" };

continue;
};

log.debug(
"expanding file path relative={s} abs={s}",
.{ path, abs },
);
}

switch (self.value.items[i]) {
.optional, .required => |*p| p.* = try alloc.dupeZ(u8, abs),
Expand Down Expand Up @@ -5793,6 +5889,23 @@ pub const TextBlending = enum {
}
};

/// See background-image-mode
///
/// This enum is used to set the background image mode. The shader expects
/// a `uint`, so we use `u8` here. The values for each mode should be kept
/// in sync with the values in the vertex shader used to render the
/// background image (`bgimage`).
pub const BackgroundImageMode = enum(u8) {
zoomed = 0,
stretched = 1,
tiled = 2,
centered = 3,
upper_left = 4,
upper_right = 5,
lower_left = 6,
lower_right = 7,
};

/// See freetype-load-flag
pub const FreetypeLoadFlags = packed struct {
// The defaults here at the time of writing this match the defaults
Expand Down
Loading

0 comments on commit 5aa9d0c

Please sign in to comment.