Skip to content

Instantly share code, notes, and snippets.

@Lightnet
Last active August 11, 2026 05:27
Show Gist options
  • Select an option

  • Save Lightnet/4ad9cfdbac26c86ecda439670dbf1569 to your computer and use it in GitHub Desktop.

Select an option

Save Lightnet/4ad9cfdbac26c86ecda439670dbf1569 to your computer and use it in GitHub Desktop.
Sample of fake terminal. Zig 0.16.0 SDL 3

Information:

zig 0.16.0, SDL 3.x

zig fetch --save=sdl https://github.com/libsdl-org/SDL/releases/download/release-3.4.14/SDL3-devel-3.4.14-VC.zip
zig fetch --save=sdl_ttf https://github.com/libsdl-org/SDL_ttf/releases/download/release-3.2.2/SDL3_ttf-devel-3.2.2-VC.zip
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// 1. Fetch dependencies declared under the corresponding names in build.zig.zon
const sdl_dep = b.dependency("sdl", .{});
const sdl_ttf_dep = b.dependency("sdl_ttf", .{});
const exe = b.addExecutable(.{
.name = "zig_sdl3_terminal",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
}),
});
const sdl_include_path = sdl_dep.path("include");
const sdl_ttf_include_path = sdl_ttf_dep.path("include");
exe.root_module.addIncludePath(sdl_include_path);
exe.root_module.addIncludePath(sdl_ttf_include_path);
// Identify target operating system and architecture
const target_os = target.result.os.tag;
if (target_os == .windows) {
// This switch turns the {s} into "x64", "x86", or "arm64"
// to match the exact directory inside the official SDL release zip
const arch_dir = switch (target.result.cpu.arch) {
.x86_64 => "x64",
.x86 => "x86",
.aarch64 => "arm64",
else => "x64",
};
// 1. Tell Zig WHERE to find SDL3.lib and SDL3_ttf.lib during compilation
const sdl_lib_dir = sdl_dep.path(b.fmt("lib/{s}", .{arch_dir}));
const ttf_lib_dir = sdl_ttf_dep.path(b.fmt("lib/{s}", .{arch_dir}));
exe.root_module.addLibraryPath(sdl_lib_dir);
exe.root_module.addLibraryPath(ttf_lib_dir);
// 2. Safely resolve paths for copying runtime DLLs to the output directory
const sdl_dll = sdl_dep.path(b.fmt("lib/{s}/SDL3.dll", .{arch_dir}));
const sdl_ttf_dll = sdl_ttf_dep.path(b.fmt("lib/{s}/SDL3_ttf.dll", .{arch_dir}));
// Automated deployment of Windows dynamic link libraries (.dll) to execution binary location
const copy_sdl = b.addInstallFileWithDir(sdl_dll, .bin, "SDL3.dll");
const copy_ttf = b.addInstallFileWithDir(sdl_ttf_dll, .bin, "SDL3_ttf.dll");
// Force the execution step to wait for these files to finish deploying
exe.step.dependOn(&copy_sdl.step);
exe.step.dependOn(&copy_ttf.step);
}
exe.root_module.linkSystemLibrary("SDL3", .{});
exe.root_module.linkSystemLibrary("SDL3_ttf", .{});
exe.root_module.link_libc = true;
b.installArtifact(exe);
const run_step = b.step("run", "Run the app");
const run_cmd = b.addRunArtifact(exe);
run_step.dependOn(&run_cmd.step);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
}
const std = @import("std");
const c = @cImport({
@cInclude("SDL3/SDL.h");
@cInclude("SDL3_ttf/SDL_ttf.h");
});
const MAX_LINES = 22;
const MAX_LINE_LEN = 256; // Expanded to accommodate longer raw command inputs before chunking
const FONT_SIZE = 20.0;
const LINE_HEIGHT = 25.0;
const WRAP_LIMIT = 70; // Maximum characters visible on a single row before wrapping down
// Struct to manage the internal state of our terminal emulator
const Terminal = struct {
log: [MAX_LINES][MAX_LINE_LEN]u8 = undefined,
log_lens: [MAX_LINES]usize = .{0} ** MAX_LINES,
log_line_count: usize = 0,
input_buffer: [MAX_LINE_LEN]u8 = undefined,
input_len: usize = 0,
// Appends a text string to our log matrix, automatically slicing it into multi-line wrapped chunks
fn appendToLog(self: *Terminal, text: []const u8) void {
var remainder = text;
// Loop and chunk the text whenever it exceeds the screen column boundary
while (remainder.len > 0) {
const chunk_len = @min(remainder.len, WRAP_LIMIT);
const chunk = remainder[0..chunk_len];
if (self.log_line_count < MAX_LINES) {
@memcpy(self.log[self.log_line_count][0..chunk_len], chunk);
self.log_lens[self.log_line_count] = chunk_len;
self.log_line_count += 1;
} else {
// Scroll all active rows up by one row index position to make room at the bottom
for (1..MAX_LINES) |i| {
self.log[i - 1] = self.log[i];
self.log_lens[i - 1] = self.log_lens[i];
}
@memcpy(self.log[MAX_LINES - 1][0..chunk_len], chunk);
self.log_lens[MAX_LINES - 1] = chunk_len;
}
remainder = remainder[chunk_len..];
}
}
// Evaluates entered string commands
fn executeCommand(self: *Terminal, cmd: []const u8, running: *bool) void {
if (std.mem.eql(u8, cmd, "clear")) {
self.log_line_count = 0;
} else if (std.mem.eql(u8, cmd, "help")) {
self.appendToLog("Available commands: help, clear, hello, status, longtext, exit");
} else if (std.mem.eql(u8, cmd, "hello")) {
self.appendToLog("Hello from Zig 0.16.0 & SDL3 text engine!");
} else if (std.mem.eql(u8, cmd, "status")) {
self.appendToLog("Systems: OPTIMAL | Core: SECURE | Language: ZIG");
} else if (std.mem.eql(u8, cmd, "longtext")) {
self.appendToLog("This is an exceptionally long sequence designed to test our brand-new automated word-wrapping subsystem matrix inside the native window pipeline seamlessly!");
} else if (std.mem.eql(u8, cmd, "exit")) {
running.* = false;
} else if (cmd.len > 0) {
var buf: [MAX_LINE_LEN + 32]u8 = undefined;
if (std.fmt.bufPrint(&buf, "Unknown command: {s}", .{cmd})) |msg| {
self.appendToLog(msg);
} else |_| {
self.appendToLog("Unknown command");
}
}
}
};
pub fn main(init: std.process.Init) !void {
_ = init;
// 1. Initialize SDL Subsystems
if (!c.SDL_Init(c.SDL_INIT_VIDEO)) {
std.debug.print("SDL Init Error: {s}\n", .{c.SDL_GetError()});
return error.SDLInitializationFailed;
}
defer c.SDL_Quit();
// 2. Initialize SDL_ttf Subsystem
if (!c.TTF_Init()) {
std.debug.print("TTF Init Error: {s}\n", .{c.SDL_GetError()});
return error.TTFInitializationFailed;
}
defer c.TTF_Quit();
// 3. Create Window and Renderer
var window: ?*c.SDL_Window = null;
var renderer: ?*c.SDL_Renderer = null;
if (!c.SDL_CreateWindowAndRenderer("Zig SDL3 Terminal Core", 800, 600, 0, &window, &renderer)) {
std.debug.print("Window/Renderer Error: {s}\n", .{c.SDL_GetError()});
return error.SDLWindowCreationFailed;
}
defer c.SDL_DestroyRenderer(renderer);
defer c.SDL_DestroyWindow(window);
// 4. Load the Font Asset
const font_path = "assets/fonts/m6x11.ttf";
const font: ?*c.TTF_Font = c.TTF_OpenFont(font_path, FONT_SIZE);
if (font == null) {
std.debug.print("Failed to load font at {s}: {s}\n", .{ font_path, c.SDL_GetError() });
return error.FontLoadFailed;
}
defer c.TTF_CloseFont(font);
// 5. Create Text Engine
const text_engine = c.TTF_CreateRendererTextEngine(renderer);
if (text_engine == null) {
std.debug.print("Failed to create text engine: {s}\n", .{c.SDL_GetError()});
return error.TextEngineCreationFailed;
}
defer c.TTF_DestroyRendererTextEngine(text_engine);
// Setup terminal instance state
var terminal = Terminal{};
terminal.appendToLog("Zig & SDL3 Terminal Interface Booted Successfully.");
terminal.appendToLog("Type 'help' to reveal available system modules.");
// Turn on the OS text input processing pipe
_ = c.SDL_StartTextInput(window);
var running = true;
var event: c.SDL_Event = undefined;
while (running) {
while (c.SDL_PollEvent(&event)) {
switch (event.type) {
c.SDL_EVENT_QUIT => {
running = false;
},
c.SDL_EVENT_TEXT_INPUT => {
const text_slice = std.mem.span(event.text.text);
if (terminal.input_len + text_slice.len < MAX_LINE_LEN) {
@memcpy(terminal.input_buffer[terminal.input_len..][0..text_slice.len], text_slice);
terminal.input_len += text_slice.len;
}
},
c.SDL_EVENT_KEY_DOWN => {
switch (event.key.key) {
c.SDLK_ESCAPE => {
running = false;
},
c.SDLK_BACKSPACE => {
if (terminal.input_len > 0) {
terminal.input_len -= 1;
}
},
c.SDLK_RETURN => {
var echo_buf: [MAX_LINE_LEN + 4]u8 = undefined;
const cmd = terminal.input_buffer[0..terminal.input_len];
// Echo prompt out to visual history log
if (std.fmt.bufPrint(&echo_buf, "> {s}", .{cmd})) |echo| {
terminal.appendToLog(echo);
} else |_| {}
terminal.executeCommand(cmd, &running);
terminal.input_len = 0; // Wipe transient input track
},
else => {},
}
},
else => {},
}
}
// Clean Canvas (Dark-tinted terminal palette setup)
_ = c.SDL_SetRenderDrawColor(renderer, 10, 20, 15, 255);
_ = c.SDL_RenderClear(renderer);
var current_y: f32 = 15.0;
// 6. Draw past terminal history rows line by line via SDL3 Text Engine
for (0..terminal.log_line_count) |i| {
const line = terminal.log[i][0..terminal.log_lens[i]];
if (line.len > 0) {
const text_obj = c.TTF_CreateText(text_engine, font, line.ptr, line.len);
if (text_obj) |t| {
_ = c.TTF_SetTextColor(t, 50, 255, 50, 255); // Matrix Green
_ = c.TTF_DrawRendererText(t, 15.0, current_y);
c.TTF_DestroyText(t);
}
}
current_y += LINE_HEIGHT;
}
// 7. Format, wrap, and paint active prompt line dynamically
var prompt_buf: [MAX_LINE_LEN + 4]u8 = undefined;
const current_input = terminal.input_buffer[0..terminal.input_len];
if (std.fmt.bufPrint(&prompt_buf, "> {s}_", .{current_input})) |prompt_str| {
var remainder = prompt_str;
// Incrementally slice and render the prompt line if it overflows the display bounds
while (remainder.len > 0) {
const chunk_len = @min(remainder.len, WRAP_LIMIT);
const chunk = remainder[0..chunk_len];
const text_obj = c.TTF_CreateText(text_engine, font, chunk.ptr, chunk.len);
if (text_obj) |t| {
_ = c.TTF_SetTextColor(t, 120, 255, 120, 255); // Bright Green
_ = c.TTF_DrawRendererText(t, 15.0, current_y);
c.TTF_DestroyText(t);
}
current_y += LINE_HEIGHT;
remainder = remainder[chunk_len..];
}
} else |_| {}
_ = c.SDL_RenderPresent(renderer);
c.SDL_Delay(16); // Constrain update intervals (~60 FPS)
}
_ = c.SDL_StopTextInput(window);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment