Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add background image support for OpenGL #4226

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
261 changes: 188 additions & 73 deletions src/config/Config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,34 @@ 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,

yunusey marked this conversation as resolved.
Show resolved Hide resolved
/// Background image mode to use.
///
/// Valid values are:
///
/// * `zoomed` - Image is scaled to fit the window, preserving aspect ratio.
/// * `stretched` - Image is stretched to fill the window, not preserving aspect ratio.
/// * `cropped` - Image is centered in the window, preserving the aspect ratio
/// but cropping the image to fill the window, as needed.
/// * `tiled` - Image is repeated horizontally and vertically to fill the window.
/// * `centered` - Image is centered in the window and displayed 1-to-1 pixel
yunusey marked this conversation as resolved.
Show resolved Hide resolved
/// 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 @@ -3328,7 +3356,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 @@ -3338,6 +3366,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 @@ -4432,6 +4540,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,

yunusey marked this conversation as resolved.
Show resolved Hide resolved
/// 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(
yunusey marked this conversation as resolved.
Show resolved Hide resolved
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);
}
};

yunusey marked this conversation as resolved.
Show resolved Hide resolved
/// 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 @@ -4663,88 +4828,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 @@ -5818,6 +5915,24 @@ 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) {
yunusey marked this conversation as resolved.
Show resolved Hide resolved
zoomed = 0,
stretched = 1,
cropped = 2,
tiled = 3,
centered = 4,
upper_left = 5,
upper_right = 6,
lower_left = 7,
lower_right = 8,
};

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