mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
Make compatible with latest Mainline, OpenGL
This commit is contained in:
@ -30,10 +30,11 @@ pub const RepeatableFontVariation = Config.RepeatableFontVariation;
|
|||||||
pub const RepeatableString = Config.RepeatableString;
|
pub const RepeatableString = Config.RepeatableString;
|
||||||
pub const RepeatableStringMap = @import("config/RepeatableStringMap.zig");
|
pub const RepeatableStringMap = @import("config/RepeatableStringMap.zig");
|
||||||
pub const RepeatablePath = Config.RepeatablePath;
|
pub const RepeatablePath = Config.RepeatablePath;
|
||||||
pub const SinglePath = Config.SinglePath;
|
pub const Path = Config.Path;
|
||||||
pub const ShellIntegrationFeatures = Config.ShellIntegrationFeatures;
|
pub const ShellIntegrationFeatures = Config.ShellIntegrationFeatures;
|
||||||
pub const WindowPaddingColor = Config.WindowPaddingColor;
|
pub const WindowPaddingColor = Config.WindowPaddingColor;
|
||||||
pub const BackgroundImageMode = Config.BackgroundImageMode;
|
pub const BackgroundImageMode = Config.BackgroundImageMode;
|
||||||
|
pub const BackgroundImagePosition = Config.BackgroundImagePosition;
|
||||||
|
|
||||||
// Alternate APIs
|
// Alternate APIs
|
||||||
pub const CAPI = @import("config/CAPI.zig");
|
pub const CAPI = @import("config/CAPI.zig");
|
||||||
|
@ -460,7 +460,7 @@ background: Color = .{ .r = 0x28, .g = 0x2C, .b = 0x34 },
|
|||||||
foreground: Color = .{ .r = 0xFF, .g = 0xFF, .b = 0xFF },
|
foreground: Color = .{ .r = 0xFF, .g = 0xFF, .b = 0xFF },
|
||||||
|
|
||||||
/// Background image for the window.
|
/// Background image for the window.
|
||||||
@"background-image": SinglePath = .{},
|
@"background-image": ?Path = null,
|
||||||
|
|
||||||
/// Background image opacity
|
/// Background image opacity
|
||||||
@"background-image-opacity": f32 = 1.0,
|
@"background-image-opacity": f32 = 1.0,
|
||||||
@ -469,23 +469,30 @@ foreground: Color = .{ .r = 0xFF, .g = 0xFF, .b = 0xFF },
|
|||||||
///
|
///
|
||||||
/// Valid values are:
|
/// Valid values are:
|
||||||
///
|
///
|
||||||
/// * `zoomed` - Image is scaled to fit the window, preserving aspect ratio.
|
/// * `contain` - Image is scaled to fit the window, preserving aspect ratio.
|
||||||
/// * `stretched` - Image is stretched to fill the window, not preserving aspect ratio.
|
/// * `fill` - Image is stretched to fill the window, not preserving aspect ratio.
|
||||||
/// * `cropped` - Image is centered in the window, preserving the aspect ratio
|
/// * `cover` - Image is centered in the window, preserving the aspect ratio
|
||||||
/// but cropping the image to fill the window, as needed.
|
/// but cropping the image to fill the window, as needed.
|
||||||
/// * `tiled` - Image is repeated horizontally and vertically to fill the window.
|
/// * `tiled` - Image is repeated horizontally and vertically to fill the window.
|
||||||
/// * `centered` - Image is centered in the window and displayed 1-to-1 pixel
|
/// * `none` - Image is displayed 1-to-1 pixel scale, preserving both the aspect
|
||||||
/// scale, preserving both the aspect ratio and the image size.
|
/// 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,
|
@"background-image-mode": BackgroundImageMode = .none,
|
||||||
|
|
||||||
|
/// Background image position.
|
||||||
|
///
|
||||||
|
/// Valid values are:
|
||||||
|
/// * `top-left`
|
||||||
|
/// * `top-center`
|
||||||
|
/// * `top-right`
|
||||||
|
/// * `center-left`
|
||||||
|
/// * `center`
|
||||||
|
/// * `center-right`
|
||||||
|
/// * `bottom-left`
|
||||||
|
/// * `bottom-center`
|
||||||
|
/// * `bottom-right`
|
||||||
|
///
|
||||||
|
@"background-image-position": BackgroundImagePosition = .center,
|
||||||
|
|
||||||
/// The foreground and background color for selection. If this is not set, then
|
/// The foreground and background color for selection. If this is not set, then
|
||||||
/// the selection color is just the inverted window background and foreground
|
/// the selection color is just the inverted window background and foreground
|
||||||
@ -3003,97 +3010,29 @@ fn expandPaths(self: *Config, base: []const u8) !void {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Expand all of our paths
|
// Expand all of our paths
|
||||||
inline for (@typeInfo(Config).Struct.fields) |field| {
|
inline for (@typeInfo(Config).@"struct".fields) |field| {
|
||||||
if (field.type == RepeatablePath) {
|
switch (field.type) {
|
||||||
try @field(self, field.name).expand(
|
RepeatablePath, Path => {
|
||||||
arena_alloc,
|
try @field(self, field.name).expand(
|
||||||
base,
|
arena_alloc,
|
||||||
&self._diagnostics,
|
base,
|
||||||
);
|
&self._diagnostics,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
?Path => {
|
||||||
|
if (@field(self, field.name)) |*path| {
|
||||||
|
try path.expand(
|
||||||
|
arena_alloc,
|
||||||
|
base,
|
||||||
|
&self._diagnostics,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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 {
|
fn loadTheme(self: *Config, theme: Theme) !void {
|
||||||
// Load the correct theme depending on the conditional state.
|
// Load the correct theme depending on the conditional state.
|
||||||
// Dark/light themes were programmed prior to conditional configuration
|
// Dark/light themes were programmed prior to conditional configuration
|
||||||
@ -4272,63 +4211,6 @@ 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
|
/// RepeatableString is a string value that can be repeated to accumulate
|
||||||
/// a list of strings. This isn't called "StringList" because I find that
|
/// a list of strings. This isn't called "StringList" because I find that
|
||||||
/// sometimes leads to confusion that it _accepts_ a list such as
|
/// sometimes leads to confusion that it _accepts_ a list such as
|
||||||
@ -4485,272 +4367,6 @@ pub const RepeatableString = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// RepeatablePath is like repeatable string but represents a path value.
|
|
||||||
/// The difference is that when loading the configuration any values for
|
|
||||||
/// this will be automatically expanded relative to the path of the config
|
|
||||||
/// file.
|
|
||||||
pub const RepeatablePath = struct {
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
const Path = union(enum) {
|
|
||||||
/// No error if the file does not exist.
|
|
||||||
optional: [:0]const u8,
|
|
||||||
|
|
||||||
/// The file is required to exist.
|
|
||||||
required: [:0]const u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
value: std.ArrayListUnmanaged(Path) = .{},
|
|
||||||
|
|
||||||
pub fn parseCLI(self: *Self, alloc: Allocator, input: ?[]const u8) !void {
|
|
||||||
const value, const optional = if (input) |value| blk: {
|
|
||||||
if (value.len == 0) {
|
|
||||||
self.value.clearRetainingCapacity();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
break :blk if (value[0] == '?')
|
|
||||||
.{ value[1..], true }
|
|
||||||
else if (value.len >= 2 and value[0] == '"' and value[value.len - 1] == '"')
|
|
||||||
.{ value[1 .. value.len - 1], false }
|
|
||||||
else
|
|
||||||
.{ value, false };
|
|
||||||
} else return error.ValueRequired;
|
|
||||||
|
|
||||||
if (value.len == 0) {
|
|
||||||
// This handles the case of zero length paths after removing any ?
|
|
||||||
// prefixes or surrounding quotes. In this case, we don't reset the
|
|
||||||
// list.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const item: Path = if (optional)
|
|
||||||
.{ .optional = try alloc.dupeZ(u8, value) }
|
|
||||||
else
|
|
||||||
.{ .required = try alloc.dupeZ(u8, value) };
|
|
||||||
|
|
||||||
try self.value.append(alloc, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deep copy of the struct. Required by Config.
|
|
||||||
pub fn clone(self: *const Self, alloc: Allocator) Allocator.Error!Self {
|
|
||||||
const value = try self.value.clone(alloc);
|
|
||||||
for (value.items) |*item| {
|
|
||||||
switch (item.*) {
|
|
||||||
.optional, .required => |*path| path.* = try alloc.dupeZ(u8, path.*),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.value = value,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compare if two of our value are requal. Required by Config.
|
|
||||||
pub fn equal(self: Self, other: Self) bool {
|
|
||||||
if (self.value.items.len != other.value.items.len) return false;
|
|
||||||
for (self.value.items, other.value.items) |a, b| {
|
|
||||||
if (!std.meta.eql(a, b)) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used by Formatter
|
|
||||||
pub fn formatEntry(self: Self, formatter: anytype) !void {
|
|
||||||
if (self.value.items.len == 0) {
|
|
||||||
try formatter.formatEntry(void, {});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf: [std.fs.max_path_bytes + 1]u8 = undefined;
|
|
||||||
for (self.value.items) |item| {
|
|
||||||
const value = switch (item) {
|
|
||||||
.optional => |path| std.fmt.bufPrint(
|
|
||||||
&buf,
|
|
||||||
"?{s}",
|
|
||||||
.{path},
|
|
||||||
) catch |err| switch (err) {
|
|
||||||
// Required for builds on Linux where NoSpaceLeft
|
|
||||||
// isn't an allowed error for fmt.
|
|
||||||
error.NoSpaceLeft => return error.OutOfMemory,
|
|
||||||
},
|
|
||||||
.required => |path| path,
|
|
||||||
};
|
|
||||||
|
|
||||||
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 {
|
|
||||||
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 },
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
// 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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test "parseCLI" {
|
|
||||||
const testing = std.testing;
|
|
||||||
var arena = ArenaAllocator.init(testing.allocator);
|
|
||||||
defer arena.deinit();
|
|
||||||
const alloc = arena.allocator();
|
|
||||||
|
|
||||||
var list: Self = .{};
|
|
||||||
try list.parseCLI(alloc, "config.1");
|
|
||||||
try list.parseCLI(alloc, "?config.2");
|
|
||||||
try list.parseCLI(alloc, "\"?config.3\"");
|
|
||||||
|
|
||||||
// Zero-length values, ignored
|
|
||||||
try list.parseCLI(alloc, "?");
|
|
||||||
try list.parseCLI(alloc, "\"\"");
|
|
||||||
|
|
||||||
try testing.expectEqual(@as(usize, 3), list.value.items.len);
|
|
||||||
|
|
||||||
const Tag = std.meta.Tag(Path);
|
|
||||||
try testing.expectEqual(Tag.required, @as(Tag, list.value.items[0]));
|
|
||||||
try testing.expectEqualStrings("config.1", list.value.items[0].required);
|
|
||||||
|
|
||||||
try testing.expectEqual(Tag.optional, @as(Tag, list.value.items[1]));
|
|
||||||
try testing.expectEqualStrings("config.2", list.value.items[1].optional);
|
|
||||||
|
|
||||||
try testing.expectEqual(Tag.required, @as(Tag, list.value.items[2]));
|
|
||||||
try testing.expectEqualStrings("?config.3", list.value.items[2].required);
|
|
||||||
|
|
||||||
try list.parseCLI(alloc, "");
|
|
||||||
try testing.expectEqual(@as(usize, 0), list.value.items.len);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "formatConfig empty" {
|
|
||||||
const testing = std.testing;
|
|
||||||
var buf = std.ArrayList(u8).init(testing.allocator);
|
|
||||||
defer buf.deinit();
|
|
||||||
|
|
||||||
var list: Self = .{};
|
|
||||||
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer()));
|
|
||||||
try std.testing.expectEqualSlices(u8, "a = \n", buf.items);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "formatConfig single item" {
|
|
||||||
const testing = std.testing;
|
|
||||||
var buf = std.ArrayList(u8).init(testing.allocator);
|
|
||||||
defer buf.deinit();
|
|
||||||
|
|
||||||
var arena = ArenaAllocator.init(testing.allocator);
|
|
||||||
defer arena.deinit();
|
|
||||||
const alloc = arena.allocator();
|
|
||||||
|
|
||||||
var list: Self = .{};
|
|
||||||
try list.parseCLI(alloc, "A");
|
|
||||||
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer()));
|
|
||||||
try std.testing.expectEqualSlices(u8, "a = A\n", buf.items);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "formatConfig multiple items" {
|
|
||||||
const testing = std.testing;
|
|
||||||
var buf = std.ArrayList(u8).init(testing.allocator);
|
|
||||||
defer buf.deinit();
|
|
||||||
|
|
||||||
var arena = ArenaAllocator.init(testing.allocator);
|
|
||||||
defer arena.deinit();
|
|
||||||
const alloc = arena.allocator();
|
|
||||||
|
|
||||||
var list: Self = .{};
|
|
||||||
try list.parseCLI(alloc, "A");
|
|
||||||
try list.parseCLI(alloc, "?B");
|
|
||||||
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer()));
|
|
||||||
try std.testing.expectEqualSlices(u8, "a = A\na = ?B\n", buf.items);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// FontVariation is a repeatable configuration value that sets a single
|
/// FontVariation is a repeatable configuration value that sets a single
|
||||||
/// font variation value. Font variations are configurations for what
|
/// font variation value. Font variations are configurations for what
|
||||||
/// are often called "variable fonts." The font files usually end in
|
/// are often called "variable fonts." The font files usually end in
|
||||||
@ -6634,15 +6250,29 @@ pub const AlphaBlending = enum {
|
|||||||
/// in sync with the values in the vertex shader used to render the
|
/// in sync with the values in the vertex shader used to render the
|
||||||
/// background image (`bgimage`).
|
/// background image (`bgimage`).
|
||||||
pub const BackgroundImageMode = enum(u8) {
|
pub const BackgroundImageMode = enum(u8) {
|
||||||
zoomed = 0,
|
contain = 0,
|
||||||
stretched = 1,
|
fill = 1,
|
||||||
cropped = 2,
|
cover = 2,
|
||||||
tiled = 3,
|
tiled = 3,
|
||||||
centered = 4,
|
none = 4,
|
||||||
upper_left = 5,
|
};
|
||||||
upper_right = 6,
|
|
||||||
lower_left = 7,
|
/// See background-image-position
|
||||||
lower_right = 8,
|
///
|
||||||
|
/// This enum is used to set the background image position. The shader expects
|
||||||
|
/// a `uint`, so we use `u8` here. The values for each position should be kept
|
||||||
|
/// in sync with the values in the vertex shader used to render the
|
||||||
|
/// background image (`bgimage`).
|
||||||
|
pub const BackgroundImagePosition = enum(u8) {
|
||||||
|
@"top-left" = 0,
|
||||||
|
@"top-center" = 1,
|
||||||
|
@"top-right" = 2,
|
||||||
|
@"center-left" = 3,
|
||||||
|
center = 4,
|
||||||
|
@"center-right" = 5,
|
||||||
|
@"bottom-left" = 6,
|
||||||
|
@"bottom-center" = 7,
|
||||||
|
@"bottom-right" = 8,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// See freetype-load-flag
|
/// See freetype-load-flag
|
||||||
|
@ -135,11 +135,14 @@ font_shaper: font.Shaper,
|
|||||||
font_shaper_cache: font.ShaperCache,
|
font_shaper_cache: font.ShaperCache,
|
||||||
|
|
||||||
/// The background image(s) to draw. Currently, we always draw the last image.
|
/// The background image(s) to draw. Currently, we always draw the last image.
|
||||||
background_image: configpkg.SinglePath,
|
background_image: ?configpkg.Path,
|
||||||
|
|
||||||
/// The background image mode to use.
|
/// The background image mode to use.
|
||||||
background_image_mode: configpkg.BackgroundImageMode,
|
background_image_mode: configpkg.BackgroundImageMode,
|
||||||
|
|
||||||
|
/// The background image position to use.
|
||||||
|
background_image_position: configpkg.BackgroundImagePosition,
|
||||||
|
|
||||||
/// The current background image to draw. If it is null, then we will not
|
/// The current background image to draw. If it is null, then we will not
|
||||||
/// draw any background image.
|
/// draw any background image.
|
||||||
current_background_image: ?Image = null,
|
current_background_image: ?Image = null,
|
||||||
@ -443,9 +446,10 @@ pub const DerivedConfig = struct {
|
|||||||
cursor_text: ?terminal.color.RGB,
|
cursor_text: ?terminal.color.RGB,
|
||||||
background: terminal.color.RGB,
|
background: terminal.color.RGB,
|
||||||
background_opacity: f64,
|
background_opacity: f64,
|
||||||
background_image: configpkg.SinglePath,
|
background_image: ?configpkg.Path,
|
||||||
background_image_opacity: f32,
|
background_image_opacity: f32,
|
||||||
background_image_mode: configpkg.BackgroundImageMode,
|
background_image_mode: configpkg.BackgroundImageMode,
|
||||||
|
background_image_position: configpkg.BackgroundImagePosition,
|
||||||
foreground: terminal.color.RGB,
|
foreground: terminal.color.RGB,
|
||||||
selection_background: ?terminal.color.RGB,
|
selection_background: ?terminal.color.RGB,
|
||||||
selection_foreground: ?terminal.color.RGB,
|
selection_foreground: ?terminal.color.RGB,
|
||||||
@ -471,7 +475,7 @@ pub const DerivedConfig = struct {
|
|||||||
const custom_shaders = try config.@"custom-shader".clone(alloc);
|
const custom_shaders = try config.@"custom-shader".clone(alloc);
|
||||||
|
|
||||||
// Copy our background image
|
// Copy our background image
|
||||||
const background_image = try config.@"background-image".clone(alloc);
|
const background_image = if (config.@"background-image") |v| try v.clone(alloc) else null;
|
||||||
|
|
||||||
// Copy our font features
|
// Copy our font features
|
||||||
const font_features = try config.@"font-feature".clone(alloc);
|
const font_features = try config.@"font-feature".clone(alloc);
|
||||||
@ -517,6 +521,7 @@ pub const DerivedConfig = struct {
|
|||||||
.background_image = background_image,
|
.background_image = background_image,
|
||||||
.background_image_opacity = config.@"background-image-opacity",
|
.background_image_opacity = config.@"background-image-opacity",
|
||||||
.background_image_mode = config.@"background-image-mode",
|
.background_image_mode = config.@"background-image-mode",
|
||||||
|
.background_image_position = config.@"background-image-position",
|
||||||
|
|
||||||
.invert_selection_fg_bg = config.@"selection-invert-fg-bg",
|
.invert_selection_fg_bg = config.@"selection-invert-fg-bg",
|
||||||
.bold_is_bright = config.@"bold-is-bright",
|
.bold_is_bright = config.@"bold-is-bright",
|
||||||
@ -623,7 +628,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
|||||||
else => @compileError("unsupported target for Metal"),
|
else => @compileError("unsupported target for Metal"),
|
||||||
};
|
};
|
||||||
layer.setProperty("device", gpu_state.device.value);
|
layer.setProperty("device", gpu_state.device.value);
|
||||||
layer.setProperty("opaque", options.config.background_opacity >= 1 and options.config.background_image.value == null);
|
layer.setProperty("opaque", options.config.background_opacity >= 1 and options.config.background_image == null);
|
||||||
layer.setProperty("displaySyncEnabled", options.config.vsync);
|
layer.setProperty("displaySyncEnabled", options.config.vsync);
|
||||||
|
|
||||||
// Set our layer's pixel format appropriately.
|
// Set our layer's pixel format appropriately.
|
||||||
@ -719,6 +724,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
|||||||
.default_background_color = options.config.background,
|
.default_background_color = options.config.background,
|
||||||
.background_image = options.config.background_image,
|
.background_image = options.config.background_image,
|
||||||
.background_image_mode = options.config.background_image_mode,
|
.background_image_mode = options.config.background_image_mode,
|
||||||
|
.background_image_position = options.config.background_image_position,
|
||||||
.cursor_color = null,
|
.cursor_color = null,
|
||||||
.default_cursor_color = options.config.cursor_color,
|
.default_cursor_color = options.config.cursor_color,
|
||||||
.cursor_invert = options.config.cursor_invert,
|
.cursor_invert = options.config.cursor_invert,
|
||||||
@ -744,7 +750,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
|||||||
.use_display_p3 = options.config.colorspace == .@"display-p3",
|
.use_display_p3 = options.config.colorspace == .@"display-p3",
|
||||||
.use_linear_blending = options.config.blending.isLinear(),
|
.use_linear_blending = options.config.blending.isLinear(),
|
||||||
.use_linear_correction = options.config.blending == .@"linear-corrected",
|
.use_linear_correction = options.config.blending == .@"linear-corrected",
|
||||||
.has_bg_image = (options.config.background_image.value != null),
|
.has_bg_image = (options.config.background_image != null),
|
||||||
.bg_image_opacity = options.config.background_image_opacity,
|
.bg_image_opacity = options.config.background_image_opacity,
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1172,15 +1178,19 @@ pub fn updateFrame(
|
|||||||
try self.prepKittyGraphics(state.terminal);
|
try self.prepKittyGraphics(state.terminal);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.current_background_image == null and
|
if (self.current_background_image == null) {
|
||||||
self.background_image.value != null)
|
if (self.background_image) |background_image| {
|
||||||
{
|
const img_path, const required = switch (background_image) {
|
||||||
self.prepBackgroundImage() catch |err| switch (err) {
|
.optional => |path| .{ path, false },
|
||||||
error.InvalidData => {
|
.required => |path| .{ path, true },
|
||||||
log.warn("invalid image data, skipping", .{});
|
};
|
||||||
},
|
self.prepBackgroundImage(img_path) catch |err| switch (err) {
|
||||||
else => return err,
|
error.InvalidData => if (required) {
|
||||||
};
|
log.err("error loading background image {s}: {}", .{ img_path, err });
|
||||||
|
},
|
||||||
|
else => return err,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have any terminal dirty flags set then we need to rebuild
|
// If we have any terminal dirty flags set then we need to rebuild
|
||||||
@ -1326,8 +1336,8 @@ pub fn updateFrame(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if we need to update our current background image
|
// Check if we need to update our current background image
|
||||||
if (self.current_background_image) |current_background_image| {
|
if (self.current_background_image) |*current_background_image| {
|
||||||
switch (current_background_image) {
|
switch (current_background_image.*) {
|
||||||
.ready => {},
|
.ready => {},
|
||||||
|
|
||||||
.pending_gray,
|
.pending_gray,
|
||||||
@ -1338,7 +1348,7 @@ pub fn updateFrame(
|
|||||||
.replace_gray_alpha,
|
.replace_gray_alpha,
|
||||||
.replace_rgb,
|
.replace_rgb,
|
||||||
.replace_rgba,
|
.replace_rgba,
|
||||||
=> try self.current_background_image.?.upload(
|
=> try current_background_image.upload(
|
||||||
self.alloc,
|
self.alloc,
|
||||||
self.gpu_state.device,
|
self.gpu_state.device,
|
||||||
self.gpu_state.default_storage_mode,
|
self.gpu_state.default_storage_mode,
|
||||||
@ -1348,7 +1358,7 @@ pub fn updateFrame(
|
|||||||
.unload_replace,
|
.unload_replace,
|
||||||
.unload_ready,
|
.unload_ready,
|
||||||
=> {
|
=> {
|
||||||
self.current_background_image.?.deinit(self.alloc);
|
current_background_image.deinit(self.alloc);
|
||||||
self.current_background_image = null;
|
self.current_background_image = null;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -1723,6 +1733,7 @@ fn drawBackgroundImage(
|
|||||||
@as(f32, @floatFromInt(self.size.terminal().height)),
|
@as(f32, @floatFromInt(self.size.terminal().height)),
|
||||||
},
|
},
|
||||||
.mode = self.background_image_mode,
|
.mode = self.background_image_mode,
|
||||||
|
.position_index = self.background_image_position,
|
||||||
}}, .{
|
}}, .{
|
||||||
// Indicate that the CPU writes to this resource but never reads it.
|
// Indicate that the CPU writes to this resource but never reads it.
|
||||||
.cpu_cache_mode = .write_combined,
|
.cpu_cache_mode = .write_combined,
|
||||||
@ -2281,10 +2292,7 @@ fn prepKittyImage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Prepares the current background image for upload
|
/// Prepares the current background image for upload
|
||||||
pub fn prepBackgroundImage(self: *Metal) !void {
|
pub fn prepBackgroundImage(self: *Metal, path: []const u8) !void {
|
||||||
// If the user doesn't have a background image, do nothing...
|
|
||||||
const path = self.background_image.value orelse return;
|
|
||||||
|
|
||||||
// Read the file content
|
// Read the file content
|
||||||
const file_content = try self.readImageContent(path);
|
const file_content = try self.readImageContent(path);
|
||||||
defer self.alloc.free(file_content);
|
defer self.alloc.free(file_content);
|
||||||
@ -2293,28 +2301,24 @@ pub fn prepBackgroundImage(self: *Metal) !void {
|
|||||||
const decoded_image: wuffs.ImageData = blk: {
|
const decoded_image: wuffs.ImageData = blk: {
|
||||||
// Extract the file extension
|
// Extract the file extension
|
||||||
const ext = std.fs.path.extension(path);
|
const ext = std.fs.path.extension(path);
|
||||||
const ext_lower = try std.ascii.allocLowerString(self.alloc, ext);
|
|
||||||
defer self.alloc.free(ext_lower);
|
|
||||||
|
|
||||||
// Match based on extension
|
// Match based on extension
|
||||||
if (std.mem.eql(u8, ext_lower, ".png")) {
|
if (std.ascii.eqlIgnoreCase(ext, ".png")) {
|
||||||
break :blk try wuffs.png.decode(self.alloc, file_content);
|
break :blk try wuffs.png.decode(self.alloc, file_content);
|
||||||
} else if (std.mem.eql(u8, ext_lower, ".jpg") or std.mem.eql(u8, ext_lower, ".jpeg")) {
|
} else if (std.ascii.eqlIgnoreCase(ext, ".jpg") or std.ascii.eqlIgnoreCase(ext, ".jpeg")) {
|
||||||
break :blk try wuffs.jpeg.decode(self.alloc, file_content);
|
break :blk try wuffs.jpeg.decode(self.alloc, file_content);
|
||||||
} else {
|
} else {
|
||||||
log.warn("unsupported image format: {s}", .{ext});
|
log.warn("unsupported image format: {s}", .{ext});
|
||||||
return error.InvalidData;
|
return error.InvalidData;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
defer self.alloc.free(decoded_image.data);
|
errdefer self.alloc.free(decoded_image.data);
|
||||||
|
|
||||||
// Copy the data into the pending state
|
// Copy the data into the pending state
|
||||||
const data = try self.alloc.dupe(u8, decoded_image.data);
|
|
||||||
errdefer self.alloc.free(data);
|
|
||||||
const pending: Image.Pending = .{
|
const pending: Image.Pending = .{
|
||||||
.width = decoded_image.width,
|
.width = decoded_image.width,
|
||||||
.height = decoded_image.height,
|
.height = decoded_image.height,
|
||||||
.data = data.ptr,
|
.data = @constCast(decoded_image.data).ptr,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Store the image
|
// Store the image
|
||||||
@ -2323,37 +2327,23 @@ pub fn prepBackgroundImage(self: *Metal) !void {
|
|||||||
|
|
||||||
/// Reads the content of the given image path and returns it
|
/// Reads the content of the given image path and returns it
|
||||||
pub fn readImageContent(self: *Metal, path: []const u8) ![]u8 {
|
pub fn readImageContent(self: *Metal, path: []const u8) ![]u8 {
|
||||||
assert(std.fs.path.isAbsolute(path));
|
|
||||||
// Open the file
|
// Open the file
|
||||||
var file = std.fs.openFileAbsolute(path, .{}) catch |err| {
|
var file = std.fs.openFileAbsolute(path, .{}) catch |err| {
|
||||||
log.warn("failed to open file: {}", .{err});
|
log.warn("failed to open file {s}: {}", .{ path, err });
|
||||||
return error.InvalidData;
|
return error.InvalidData;
|
||||||
};
|
};
|
||||||
defer file.close();
|
defer file.close();
|
||||||
|
|
||||||
// File must be a regular file
|
|
||||||
if (file.stat()) |stat| {
|
|
||||||
if (stat.kind != .file) {
|
|
||||||
log.warn("file is not a regular file kind={}", .{stat.kind});
|
|
||||||
return error.InvalidData;
|
|
||||||
}
|
|
||||||
} else |err| {
|
|
||||||
log.warn("failed to stat file: {}", .{err});
|
|
||||||
return error.InvalidData;
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf_reader = std.io.bufferedReader(file.reader());
|
var buf_reader = std.io.bufferedReader(file.reader());
|
||||||
const reader = buf_reader.reader();
|
const reader = buf_reader.reader();
|
||||||
|
|
||||||
// Read the file
|
// Read the file
|
||||||
var managed = std.ArrayList(u8).init(self.alloc);
|
const image_content = reader.readAllAlloc(self.alloc, max_image_size) catch |err| {
|
||||||
errdefer managed.deinit();
|
|
||||||
reader.readAllArrayList(&managed, max_image_size) catch |err| {
|
|
||||||
log.warn("failed to read file: {}", .{err});
|
log.warn("failed to read file: {}", .{err});
|
||||||
return error.InvalidData;
|
return error.InvalidData;
|
||||||
};
|
};
|
||||||
|
|
||||||
return managed.toOwnedSlice();
|
return image_content;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the configuration.
|
/// Update the configuration.
|
||||||
@ -2392,9 +2382,10 @@ pub fn changeConfig(self: *Metal, config: *DerivedConfig) !void {
|
|||||||
|
|
||||||
// Reset current background image
|
// Reset current background image
|
||||||
self.background_image = config.background_image;
|
self.background_image = config.background_image;
|
||||||
self.uniforms.has_bg_image = (config.background_image.value != null);
|
self.uniforms.has_bg_image = (config.background_image != null);
|
||||||
self.uniforms.bg_image_opacity = config.background_image_opacity;
|
self.uniforms.bg_image_opacity = config.background_image_opacity;
|
||||||
self.background_image_mode = config.background_image_mode;
|
self.background_image_mode = config.background_image_mode;
|
||||||
|
self.background_image_position = config.background_image_position;
|
||||||
if (self.current_background_image) |*img| {
|
if (self.current_background_image) |*img| {
|
||||||
img.markForUnload();
|
img.markForUnload();
|
||||||
}
|
}
|
||||||
@ -2408,7 +2399,7 @@ pub fn changeConfig(self: *Metal, config: *DerivedConfig) !void {
|
|||||||
CATransaction.msgSend(void, "begin", .{});
|
CATransaction.msgSend(void, "begin", .{});
|
||||||
defer CATransaction.msgSend(void, "commit", .{});
|
defer CATransaction.msgSend(void, "commit", .{});
|
||||||
|
|
||||||
self.layer.setProperty("opaque", config.background_opacity >= 1 and config.background_image.value == null);
|
self.layer.setProperty("opaque", config.background_opacity >= 1 and config.background_image == null);
|
||||||
self.layer.setProperty("displaySyncEnabled", config.vsync);
|
self.layer.setProperty("displaySyncEnabled", config.vsync);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,6 +117,7 @@ pub const Image = extern struct {
|
|||||||
pub const BgImage = extern struct {
|
pub const BgImage = extern struct {
|
||||||
terminal_size: [2]f32,
|
terminal_size: [2]f32,
|
||||||
mode: configpkg.BackgroundImageMode,
|
mode: configpkg.BackgroundImageMode,
|
||||||
|
position_index: configpkg.BackgroundImagePosition,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The uniforms that are passed to the terminal cell shader.
|
/// The uniforms that are passed to the terminal cell shader.
|
||||||
|
@ -685,20 +685,17 @@ fragment float4 image_fragment(
|
|||||||
#pragma mark - BG Image Shader
|
#pragma mark - BG Image Shader
|
||||||
|
|
||||||
enum BgImageMode : uint8_t {
|
enum BgImageMode : uint8_t {
|
||||||
MODE_ZOOMED = 0u,
|
MODE_CONTAIN = 0u,
|
||||||
MODE_STRETCHED = 1u,
|
MODE_FILL = 1u,
|
||||||
MODE_CROPPED = 2u,
|
MODE_COVER = 2u,
|
||||||
MODE_TILED = 3u,
|
MODE_TILED = 3u,
|
||||||
MODE_CENTERED = 4u,
|
MODE_NONE = 4u,
|
||||||
MODE_UPPER_LEFT = 5u,
|
|
||||||
MODE_UPPER_RIGHT = 6u,
|
|
||||||
MODE_LOWER_LEFT = 7u,
|
|
||||||
MODE_LOWER_RIGHT = 8u,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BgImageVertexIn {
|
struct BgImageVertexIn {
|
||||||
float2 terminal_size [[attribute(0)]];
|
float2 terminal_size [[attribute(0)]];
|
||||||
uint8_t mode [[attribute(1)]];
|
uint8_t mode [[attribute(1)]];
|
||||||
|
uint8_t position_index [[attribute(2)]];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BgImageVertexOut {
|
struct BgImageVertexOut {
|
||||||
@ -709,7 +706,7 @@ struct BgImageVertexOut {
|
|||||||
vertex BgImageVertexOut bg_image_vertex(
|
vertex BgImageVertexOut bg_image_vertex(
|
||||||
uint vid [[vertex_id]],
|
uint vid [[vertex_id]],
|
||||||
BgImageVertexIn in [[stage_in]],
|
BgImageVertexIn in [[stage_in]],
|
||||||
texture2d<uint> image [[texture(0)]],
|
texture2d<float> image [[texture(0)]],
|
||||||
constant Uniforms& uniforms [[buffer(1)]]
|
constant Uniforms& uniforms [[buffer(1)]]
|
||||||
) {
|
) {
|
||||||
BgImageVertexOut out;
|
BgImageVertexOut out;
|
||||||
@ -732,7 +729,7 @@ vertex BgImageVertexOut bg_image_vertex(
|
|||||||
);
|
);
|
||||||
|
|
||||||
switch (in.mode) {
|
switch (in.mode) {
|
||||||
case MODE_ZOOMED: {
|
case MODE_CONTAIN: {
|
||||||
// Scale to fit the terminal size
|
// Scale to fit the terminal size
|
||||||
if (aspect_ratio.x > aspect_ratio.y) {
|
if (aspect_ratio.x > aspect_ratio.y) {
|
||||||
scale.x = aspect_ratio.y / aspect_ratio.x;
|
scale.x = aspect_ratio.y / aspect_ratio.x;
|
||||||
@ -741,7 +738,7 @@ vertex BgImageVertexOut bg_image_vertex(
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MODE_CROPPED: {
|
case MODE_COVER: {
|
||||||
// Scale to fit the terminal size
|
// Scale to fit the terminal size
|
||||||
if (aspect_ratio.x < aspect_ratio.y) {
|
if (aspect_ratio.x < aspect_ratio.y) {
|
||||||
scale.x = aspect_ratio.y / aspect_ratio.x;
|
scale.x = aspect_ratio.y / aspect_ratio.x;
|
||||||
@ -750,17 +747,13 @@ vertex BgImageVertexOut bg_image_vertex(
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MODE_CENTERED:
|
case MODE_NONE: {
|
||||||
case MODE_UPPER_LEFT:
|
|
||||||
case MODE_UPPER_RIGHT:
|
|
||||||
case MODE_LOWER_LEFT:
|
|
||||||
case MODE_LOWER_RIGHT: {
|
|
||||||
// Scale to match the actual size of the image
|
// Scale to match the actual size of the image
|
||||||
scale.x = image_size.x / in.terminal_size.x;
|
scale.x = image_size.x / in.terminal_size.x;
|
||||||
scale.y = image_size.y / in.terminal_size.y;
|
scale.y = image_size.y / in.terminal_size.y;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MODE_STRETCHED:
|
case MODE_FILL:
|
||||||
case MODE_TILED:
|
case MODE_TILED:
|
||||||
// No adjustments needed
|
// No adjustments needed
|
||||||
break;
|
break;
|
||||||
@ -768,28 +761,9 @@ vertex BgImageVertexOut bg_image_vertex(
|
|||||||
|
|
||||||
float2 final_image_size = in.terminal_size * position * scale;
|
float2 final_image_size = in.terminal_size * position * scale;
|
||||||
|
|
||||||
float2 offset = float2(0.0, 0.0);
|
uint y_pos = in.position_index / 3u; // 0 = top, 1 = center, 2 = bottom
|
||||||
switch (in.mode) {
|
uint x_pos = in.position_index % 3u; // 0 = left, 1 = center, 2 = right
|
||||||
case MODE_ZOOMED:
|
float2 offset = float2(x_pos, y_pos) * (in.terminal_size) * (1.0 - scale) / 2.0;
|
||||||
case MODE_CROPPED:
|
|
||||||
case MODE_STRETCHED:
|
|
||||||
case MODE_TILED:
|
|
||||||
case MODE_CENTERED:
|
|
||||||
offset = (in.terminal_size * (1.0 - scale)) / 2.0;
|
|
||||||
break;
|
|
||||||
case MODE_UPPER_LEFT:
|
|
||||||
offset = float2(0.0, 0.0);
|
|
||||||
break;
|
|
||||||
case MODE_UPPER_RIGHT:
|
|
||||||
offset = float2(in.terminal_size.x - image_size.x, 0.0);
|
|
||||||
break;
|
|
||||||
case MODE_LOWER_LEFT:
|
|
||||||
offset = float2(0.0, in.terminal_size.y - image_size.y);
|
|
||||||
break;
|
|
||||||
case MODE_LOWER_RIGHT:
|
|
||||||
offset = float2(in.terminal_size.x - image_size.x, in.terminal_size.y - image_size.y);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
out.position = uniforms.projection_matrix * float4(final_image_size + offset, 0.0, 1.0);
|
out.position = uniforms.projection_matrix * float4(final_image_size + offset, 0.0, 1.0);
|
||||||
out.tex_coord = position;
|
out.tex_coord = position;
|
||||||
@ -802,12 +776,15 @@ vertex BgImageVertexOut bg_image_vertex(
|
|||||||
|
|
||||||
fragment float4 bg_image_fragment(
|
fragment float4 bg_image_fragment(
|
||||||
BgImageVertexOut in [[stage_in]],
|
BgImageVertexOut in [[stage_in]],
|
||||||
texture2d<uint> image [[texture(0)]],
|
texture2d<float> image [[texture(0)]],
|
||||||
constant Uniforms& uniforms [[buffer(1)]]
|
constant Uniforms& uniforms [[buffer(1)]]
|
||||||
) {
|
) {
|
||||||
constexpr sampler textureSampler(address::repeat, filter::linear);
|
constexpr sampler textureSampler(address::repeat, filter::linear);
|
||||||
float2 norm_coord = fract(in.tex_coord);
|
float2 norm_coord = fract(in.tex_coord);
|
||||||
float4 color = float4(image.sample(textureSampler, norm_coord)) / 255.0f;
|
float4 color = image.sample(textureSampler, norm_coord);
|
||||||
|
if (!uniforms.use_linear_blending) {
|
||||||
|
color = unlinearize(color);
|
||||||
|
}
|
||||||
|
|
||||||
return float4(color.rgb * color.a * uniforms.bg_image_opacity, color.a * uniforms.bg_image_opacity);
|
return float4(color.rgb * color.a * uniforms.bg_image_opacity, color.a * uniforms.bg_image_opacity);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user