feat(gui): M1 shared core — extract src/gui/ and replace -Dgui with -Drenderer
Move shared types out of src/win32/ into a new src/gui/ package:
- xy.zig: generic XY(T) coordinate type
- xterm.zig: 256-color palette table (used by all GPU renderers)
- GlyphIndexCache.zig: glyph→atlas-slot mapping
- Cell.zig: Rgba8 and Cell extracted from d3d11.zig
- GlyphRasterizer.zig: comptime interface spec/checker
Update src/win32/ imports to point to ../gui/ and delete the
moved originals.
Replace the -Dgui bool option with -Drenderer enum { terminal, gui,
d3d11 }, wiring .d3d11 to the existing win32 DirectWrite path and
keeping build_options.gui = (renderer != .terminal) for tui.zig.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a782bfb690
commit
9c66b19650
9 changed files with 101 additions and 44 deletions
40
build.zig
40
build.zig
|
|
@ -3,6 +3,8 @@ const builtin = @import("builtin");
|
|||
|
||||
const optimize_deps = .ReleaseFast;
|
||||
|
||||
pub const Renderer = enum { terminal, gui, d3d11 };
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
const all_targets = b.option(bool, "all_targets", "Build all known good targets during release builds (default: no)") orelse false;
|
||||
const tracy_enabled = b.option(bool, "enable_tracy", "Enable tracy client library (default: no)") orelse false;
|
||||
|
|
@ -10,7 +12,7 @@ pub fn build(b: *std.Build) void {
|
|||
const strip = b.option(bool, "strip", "Disable debug information (default: no)");
|
||||
const use_llvm = b.option(bool, "use_llvm", "Enable llvm backend (default: none)");
|
||||
const pie = b.option(bool, "pie", "Produce an executable with position independent code (default: none)");
|
||||
const gui = b.option(bool, "gui", "Standalone GUI mode") orelse false;
|
||||
const renderer = b.option(Renderer, "renderer", "Renderer backend: terminal (TUI, default), d3d11 (GPU on Windows via DirectWrite), gui (GPU on Linux/macOS/Windows via wio+sokol_gfx)") orelse .terminal;
|
||||
const test_filters = b.option([]const []const u8, "test-filter", "Skip tests that do not match any filter") orelse &[0][]const u8{};
|
||||
|
||||
const run_step = b.step("run", "Run the app");
|
||||
|
|
@ -47,7 +49,7 @@ pub fn build(b: *std.Build) void {
|
|||
strip,
|
||||
use_llvm,
|
||||
pie,
|
||||
gui,
|
||||
renderer,
|
||||
version.items,
|
||||
all_targets,
|
||||
test_filters,
|
||||
|
|
@ -65,7 +67,7 @@ fn build_development(
|
|||
strip: ?bool,
|
||||
use_llvm: ?bool,
|
||||
pie: ?bool,
|
||||
gui: bool,
|
||||
renderer: Renderer,
|
||||
version: []const u8,
|
||||
_: bool, // all_targets
|
||||
test_filters: []const []const u8,
|
||||
|
|
@ -87,7 +89,7 @@ fn build_development(
|
|||
strip orelse false,
|
||||
use_llvm,
|
||||
pie,
|
||||
gui,
|
||||
renderer,
|
||||
version,
|
||||
test_filters,
|
||||
);
|
||||
|
|
@ -104,7 +106,7 @@ fn build_release(
|
|||
_: ?bool, //release builds control strip
|
||||
use_llvm: ?bool,
|
||||
pie: ?bool,
|
||||
_: bool, //gui
|
||||
_: Renderer, //renderer
|
||||
version: []const u8,
|
||||
all_targets: bool,
|
||||
test_filters: []const []const u8,
|
||||
|
|
@ -171,7 +173,7 @@ fn build_release(
|
|||
true, // strip release builds
|
||||
use_llvm,
|
||||
pie,
|
||||
false, //gui
|
||||
.terminal,
|
||||
version,
|
||||
test_filters,
|
||||
);
|
||||
|
|
@ -190,7 +192,7 @@ fn build_release(
|
|||
false, // don't strip debug builds
|
||||
use_llvm,
|
||||
pie,
|
||||
false, //gui
|
||||
.terminal,
|
||||
version,
|
||||
test_filters,
|
||||
);
|
||||
|
|
@ -210,7 +212,7 @@ fn build_release(
|
|||
true, // strip release builds
|
||||
use_llvm,
|
||||
pie,
|
||||
true, //gui
|
||||
.d3d11,
|
||||
version,
|
||||
test_filters,
|
||||
);
|
||||
|
|
@ -229,7 +231,7 @@ fn build_release(
|
|||
false, // don't strip debug builds
|
||||
use_llvm,
|
||||
pie,
|
||||
true, //gui
|
||||
.d3d11,
|
||||
version,
|
||||
test_filters,
|
||||
);
|
||||
|
|
@ -251,7 +253,7 @@ pub fn build_exe(
|
|||
strip: bool,
|
||||
use_llvm: ?bool,
|
||||
pie: ?bool,
|
||||
gui: bool,
|
||||
renderer: Renderer,
|
||||
version: []const u8,
|
||||
test_filters: []const []const u8,
|
||||
) void {
|
||||
|
|
@ -259,7 +261,7 @@ pub fn build_exe(
|
|||
options.addOption(bool, "enable_tracy", tracy_enabled);
|
||||
options.addOption(bool, "use_tree_sitter", use_tree_sitter);
|
||||
options.addOption(bool, "strip", strip);
|
||||
options.addOption(bool, "gui", gui);
|
||||
options.addOption(bool, "gui", renderer != .terminal);
|
||||
|
||||
const options_mod = options.createModule();
|
||||
|
||||
|
|
@ -489,8 +491,9 @@ pub fn build_exe(
|
|||
});
|
||||
|
||||
const renderer_mod = blk: {
|
||||
if (gui) switch (target.result.os.tag) {
|
||||
.windows => {
|
||||
switch (renderer) {
|
||||
.terminal => break :blk tui_renderer_mod,
|
||||
.d3d11 => {
|
||||
const win32_dep = b.lazyDependency("win32", .{}) orelse break :blk tui_renderer_mod;
|
||||
const win32_mod = win32_dep.module("win32");
|
||||
const gui_mod = b.createModule(.{
|
||||
|
|
@ -526,12 +529,11 @@ pub fn build_exe(
|
|||
});
|
||||
break :blk mod;
|
||||
},
|
||||
else => |tag| {
|
||||
std.log.err("OS '{t}' does not support -Dgui mode", .{tag});
|
||||
.gui => {
|
||||
std.log.err("-Drenderer=gui is not yet implemented", .{});
|
||||
std.process.exit(0xff);
|
||||
},
|
||||
};
|
||||
break :blk tui_renderer_mod;
|
||||
}
|
||||
};
|
||||
|
||||
const keybind_mod = b.createModule(.{
|
||||
|
|
@ -681,7 +683,7 @@ pub fn build_exe(
|
|||
},
|
||||
});
|
||||
|
||||
const exe_name = if (gui) "flow-gui" else "flow";
|
||||
const exe_name = if (renderer != .terminal) "flow-gui" else "flow";
|
||||
|
||||
const exe = b.addExecutable(.{
|
||||
.name = exe_name,
|
||||
|
|
@ -727,7 +729,7 @@ pub fn build_exe(
|
|||
exe.addWin32ResourceFile(.{
|
||||
.file = b.path("src/win32/flow.rc"),
|
||||
});
|
||||
if (gui) {
|
||||
if (renderer != .terminal) {
|
||||
exe.subsystem = .Windows;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
20
src/gui/Cell.zig
Normal file
20
src/gui/Cell.zig
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// Shared GPU cell types used by all GPU renderer backends.
|
||||
|
||||
pub const Rgba8 = packed struct(u32) {
|
||||
a: u8,
|
||||
b: u8,
|
||||
g: u8,
|
||||
r: u8,
|
||||
pub fn initRgb(r: u8, g: u8, b: u8) Rgba8 {
|
||||
return .{ .r = r, .g = g, .b = b, .a = 255 };
|
||||
}
|
||||
pub fn initRgba(r: u8, g: u8, b: u8, a: u8) Rgba8 {
|
||||
return .{ .r = r, .g = g, .b = b, .a = a };
|
||||
}
|
||||
};
|
||||
|
||||
pub const Cell = extern struct {
|
||||
glyph_index: u32,
|
||||
background: Rgba8,
|
||||
foreground: Rgba8,
|
||||
};
|
||||
50
src/gui/GlyphRasterizer.zig
Normal file
50
src/gui/GlyphRasterizer.zig
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
// GlyphRasterizer — comptime interface specification.
|
||||
//
|
||||
// Zig's duck-typing means no vtable is needed. This file documents the
|
||||
// interface that every rasterizer implementation must satisfy and can be
|
||||
// used as a comptime checker.
|
||||
//
|
||||
// A rasterizer implementation must provide:
|
||||
//
|
||||
// pub const Font = <type>; // font handle (scale, metrics, etc.)
|
||||
// pub const Fonts = <type>; // font enumeration (for UI font picker)
|
||||
//
|
||||
// pub fn init(allocator: std.mem.Allocator) !Self
|
||||
// pub fn deinit(self: *Self) void
|
||||
//
|
||||
// pub fn loadFont(self: *Self, name: []const u8, size_px: u16) !Font
|
||||
// Load a named font at a given pixel height. The returned Font
|
||||
// contains pre-computed metrics (cell_size, scale, ascent_px).
|
||||
//
|
||||
// pub fn render(
|
||||
// self: *const Self,
|
||||
// font: Font,
|
||||
// codepoint: u21,
|
||||
// kind: enum { single, left, right },
|
||||
// staging_buf: []u8,
|
||||
// ) void
|
||||
// Rasterize a single glyph into the caller-provided A8 staging buffer.
|
||||
// - staging_buf is zero-filled by the caller before each call.
|
||||
// - For `single`: buffer is cell_size.x * cell_size.y bytes.
|
||||
// - For `left`/`right`: buffer is 2*cell_size.x * cell_size.y bytes.
|
||||
// `left` rasterizes at double width into the full buffer.
|
||||
// `right` places the glyph offset by cell_size.x so that the right
|
||||
// half of the buffer contains the right portion of the glyph.
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
pub fn check(comptime Rasterizer: type) void {
|
||||
const has_Font = @hasDecl(Rasterizer, "Font");
|
||||
const has_Fonts = @hasDecl(Rasterizer, "Fonts");
|
||||
const has_init = @hasDecl(Rasterizer, "init");
|
||||
const has_deinit = @hasDecl(Rasterizer, "deinit");
|
||||
const has_loadFont = @hasDecl(Rasterizer, "loadFont");
|
||||
const has_render = @hasDecl(Rasterizer, "render");
|
||||
|
||||
if (!has_Font) @compileError("Rasterizer missing: pub const Font");
|
||||
if (!has_Fonts) @compileError("Rasterizer missing: pub const Fonts");
|
||||
if (!has_init) @compileError("Rasterizer missing: pub fn init");
|
||||
if (!has_deinit) @compileError("Rasterizer missing: pub fn deinit");
|
||||
if (!has_loadFont) @compileError("Rasterizer missing: pub fn loadFont");
|
||||
if (!has_render) @compileError("Rasterizer missing: pub fn render");
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ const win32 = @import("win32").everything;
|
|||
const win32ext = @import("win32ext.zig");
|
||||
|
||||
const dwrite = @import("dwrite.zig");
|
||||
const XY = @import("xy.zig").XY;
|
||||
const XY = @import("../gui/xy.zig").XY;
|
||||
|
||||
pub const Font = dwrite.Font;
|
||||
pub const Fonts = dwrite.Fonts;
|
||||
|
|
|
|||
|
|
@ -4,10 +4,11 @@ const win32 = @import("win32").everything;
|
|||
const win32ext = @import("win32ext.zig");
|
||||
|
||||
const dwrite = @import("dwrite.zig");
|
||||
const GlyphIndexCache = @import("GlyphIndexCache.zig");
|
||||
const GlyphIndexCache = @import("../gui/GlyphIndexCache.zig");
|
||||
const TextRenderer = @import("DwriteRenderer.zig");
|
||||
|
||||
const XY = @import("xy.zig").XY;
|
||||
const XY = @import("../gui/xy.zig").XY;
|
||||
const gui_cell = @import("../gui/Cell.zig");
|
||||
|
||||
pub const Font = TextRenderer.Font;
|
||||
pub const Fonts = TextRenderer.Fonts;
|
||||
|
|
@ -31,21 +32,9 @@ const global = struct {
|
|||
var background: Rgba8 = .{ .r = 19, .g = 19, .b = 19, .a = 255 };
|
||||
};
|
||||
|
||||
pub const Color = Rgba8;
|
||||
const Rgba8 = packed struct(u32) {
|
||||
a: u8,
|
||||
b: u8,
|
||||
g: u8,
|
||||
r: u8,
|
||||
pub fn initRgb(r: u8, g: u8, b: u8) Color {
|
||||
return .{ .r = r, .g = g, .b = b, .a = 255 };
|
||||
}
|
||||
pub fn initRgba(r: u8, g: u8, b: u8, a: u8) Color {
|
||||
return .{ .r = r, .g = g, .b = b, .a = a };
|
||||
}
|
||||
};
|
||||
|
||||
pub const Cell = shader.Cell;
|
||||
pub const Color = gui_cell.Rgba8;
|
||||
const Rgba8 = gui_cell.Rgba8;
|
||||
pub const Cell = gui_cell.Cell;
|
||||
|
||||
// types shared with the shader
|
||||
pub const shader = struct {
|
||||
|
|
@ -54,11 +43,7 @@ pub const shader = struct {
|
|||
col_count: u32,
|
||||
row_count: u32,
|
||||
};
|
||||
pub const Cell = extern struct {
|
||||
glyph_index: u32,
|
||||
background: Rgba8,
|
||||
foreground: Rgba8,
|
||||
};
|
||||
pub const Cell = gui_cell.Cell;
|
||||
};
|
||||
|
||||
const swap_chain_flags: u32 = @intFromEnum(win32.DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT);
|
||||
|
|
|
|||
|
|
@ -18,10 +18,10 @@ const input = @import("input");
|
|||
const windowmsg = @import("windowmsg.zig");
|
||||
|
||||
const render = @import("d3d11.zig");
|
||||
const xterm = @import("xterm.zig");
|
||||
const xterm = @import("../gui/xterm.zig");
|
||||
|
||||
const FontFace = @import("FontFace.zig");
|
||||
const XY = @import("xy.zig").XY;
|
||||
const XY = @import("../gui/xy.zig").XY;
|
||||
|
||||
const WM_APP_EXIT = win32.WM_APP + 1;
|
||||
const WM_APP_SET_BACKGROUND = win32.WM_APP + 2;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue