Skip to content

Instantly share code, notes, and snippets.

@Lightnet
Last active June 19, 2026 06:52
Show Gist options
  • Select an option

  • Save Lightnet/1ee7d390f1d119645bf82b1d5c60818d to your computer and use it in GitHub Desktop.

Select an option

Save Lightnet/1ee7d390f1d119645bf82b1d5c60818d to your computer and use it in GitHub Desktop.
Zig SDL 3 TTF fake window test sample.

Information:

Prebuild files for sdl3.dll. Use SDL3-devel-3.4.10-mingw.tar.gz

Project Folder:

- src
 - main.zig
- build.zig
- SDL3_ttf.dll
- SDL3.dll
- sdl folder
  - include
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "zig-sdl3-ttf-demo",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
}),
});
exe.root_module.link_libc = true; // Does the exact same thing under the hood
// 2. 🚀 ADD LOCAL PATHS: Tell Zig to search the root project directory for libraries
// Replace "." with a subfolder path like "libs" if you group them there
exe.root_module.addLibraryPath(b.path("."));
// Also tell Zig where to find your C header folders so @cInclude doesn't break
exe.root_module.addIncludePath(b.path("sdl/include/"));
// Link the SDL3 system libraries
// 🚀 FIXED: Route system linking through root_module with an option configuration
exe.root_module.linkSystemLibrary("SDL3", .{});
exe.root_module.linkSystemLibrary("SDL3_ttf", .{});
// exe.root_module.linkLibC();
// 2. FIXED: linkLibC remains on the 'exe' compilation step itself
// exe.linkLibC();
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");
});
// ====================== FAKE WINDOW PANEL MODULE ======================
const FakeWindow = struct {
x: f32 = 150,
y: f32 = 120,
width: f32 = 500,
height: f32 = 320,
is_dragging: bool = false,
drag_offset_x: f32 = 0,
drag_offset_y: f32 = 0,
close_hovered: bool = false,
const Self = @This();
pub fn handleEvent(self: *Self, event: *c.SDL_Event) bool {
switch (event.type) {
// c.SDL_EVENT_QUIT => return true,
c.SDL_EVENT_MOUSE_BUTTON_DOWN => {
if (event.button.button != c.SDL_BUTTON_LEFT) return false;
const mx: f32 = event.button.x;
const my: f32 = event.button.y;
const title_bar = c.SDL_FRect{ .x = self.x, .y = self.y, .w = self.width, .h = 42 };
// Close button
const close_btn = c.SDL_FRect{ .x = self.x + self.width - 50, .y = self.y + 8, .w = 36, .h = 28 };
if (mx >= close_btn.x and mx <= close_btn.x + close_btn.w and
my >= close_btn.y and my <= close_btn.y + close_btn.h)
{
return true; // Request close
}
// Start dragging on title bar
if (mx >= title_bar.x and mx <= title_bar.x + title_bar.w and
my >= title_bar.y and my <= title_bar.y + title_bar.h)
{
if (!(mx >= close_btn.x and mx <= close_btn.x + close_btn.w)) {
self.is_dragging = true;
self.drag_offset_x = mx - self.x;
self.drag_offset_y = my - self.y;
}
}
},
c.SDL_EVENT_MOUSE_BUTTON_UP => {
if (event.button.button == c.SDL_BUTTON_LEFT) {
self.is_dragging = false;
}
},
c.SDL_EVENT_MOUSE_MOTION => {
const mx: f32 = event.motion.x;
const my: f32 = event.motion.y;
if (self.is_dragging) {
self.x = mx - self.drag_offset_x;
self.y = my - self.drag_offset_y;
} else {
const close_btn = c.SDL_FRect{ .x = self.x + self.width - 50, .y = self.y + 8, .w = 36, .h = 28 };
self.close_hovered = (mx >= close_btn.x and mx <= close_btn.x + close_btn.w and
my >= close_btn.y and my <= close_btn.y + close_btn.h);
}
},
else => {},
}
return false;
}
pub fn render(self: *const Self, renderer: ?*c.SDL_Renderer, font: ?*c.TTF_Font, close_font: ?*c.TTF_Font) void {
const panel = c.SDL_FRect{ .x = self.x, .y = self.y, .w = self.width, .h = self.height };
// Shadow
const shadow = c.SDL_FRect{ .x = self.x + 10, .y = self.y + 10, .w = self.width, .h = self.height };
_ = c.SDL_SetRenderDrawColor(renderer, 0, 0, 0, 100);
_ = c.SDL_RenderFillRect(renderer, &shadow);
// Panel body
_ = c.SDL_SetRenderDrawColor(renderer, 45, 55, 70, 255);
_ = c.SDL_RenderFillRect(renderer, &panel);
// Border
_ = c.SDL_SetRenderDrawColor(renderer, 100, 180, 255, 255);
_ = c.SDL_RenderRect(renderer, &panel);
// Title bar
const title_bar = c.SDL_FRect{ .x = self.x, .y = self.y, .w = self.width, .h = 42 };
_ = c.SDL_SetRenderDrawColor(renderer, 30, 100, 180, 255);
_ = c.SDL_RenderFillRect(renderer, &title_bar);
// Title bar separator
_ = c.SDL_SetRenderDrawColor(renderer, 80, 160, 255, 255);
_ = c.SDL_RenderLine(renderer, title_bar.x, title_bar.y + title_bar.h - 1, title_bar.x + title_bar.w, title_bar.y + title_bar.h - 1);
// Close Button
const close_btn = c.SDL_FRect{ .x = self.x + self.width - 50, .y = self.y + 8, .w = 36, .h = 28 };
if (self.close_hovered) {
_ = c.SDL_SetRenderDrawColor(renderer, 220, 50, 50, 255);
} else {
_ = c.SDL_SetRenderDrawColor(renderer, 200, 60, 60, 255);
}
_ = c.SDL_RenderFillRect(renderer, &close_btn);
if (self.close_hovered) {
_ = c.SDL_SetRenderDrawColor(renderer, 255, 120, 120, 255);
} else {
_ = c.SDL_SetRenderDrawColor(renderer, 255, 100, 100, 255);
}
_ = c.SDL_RenderRect(renderer, &close_btn);
// × symbol
if (close_font) |f| {
const surf = c.TTF_RenderText_Blended(f, "×", 0, c.SDL_Color{ .r = 255, .g = 255, .b = 255, .a = 255 });
if (surf) |s| {
defer c.SDL_DestroySurface(s);
if (c.SDL_CreateTextureFromSurface(renderer, s)) |tex| {
defer c.SDL_DestroyTexture(tex);
const rect = c.SDL_FRect{
.x = close_btn.x + (close_btn.w - @as(f32, @floatFromInt(s.*.w))) / 2,
.y = close_btn.y + (close_btn.h - @as(f32, @floatFromInt(s.*.h))) / 2 - 2,
.w = @floatFromInt(s.*.w),
.h = @floatFromInt(s.*.h),
};
_ = c.SDL_RenderTexture(renderer, tex, null, &rect);
}
}
}
// Title text
if (font) |f| {
const surf = c.TTF_RenderText_Blended(f, "Panel Title", 0, c.SDL_Color{ .r = 255, .g = 255, .b = 255, .a = 255 });
if (surf) |s| {
defer c.SDL_DestroySurface(s);
if (c.SDL_CreateTextureFromSurface(renderer, s)) |tex| {
defer c.SDL_DestroyTexture(tex);
const rect = c.SDL_FRect{
.x = self.x + 16,
.y = self.y + 9,
.w = @floatFromInt(s.*.w),
.h = @floatFromInt(s.*.h),
};
_ = c.SDL_RenderTexture(renderer, tex, null, &rect);
}
}
}
}
};
// ========================== MAIN ==========================
pub fn main() !void {
if (!c.SDL_Init(c.SDL_INIT_VIDEO)) return error.SDLInitFailed;
defer c.SDL_Quit();
if (!c.TTF_Init()) return error.TTFInitFailed;
defer c.TTF_Quit();
var window: ?*c.SDL_Window = null;
var renderer: ?*c.SDL_Renderer = null;
if (!c.SDL_CreateWindowAndRenderer("Zig Modular UI Demo", 800, 600, 0, &window, &renderer)) {
return error.WindowCreationFailed;
}
defer c.SDL_DestroyRenderer(renderer);
defer c.SDL_DestroyWindow(window);
const font = c.TTF_OpenFont("Kenney Pixel.ttf", 32);
const close_font = c.TTF_OpenFont("Kenney Pixel.ttf", 28);
defer if (font != null) c.TTF_CloseFont(font);
defer if (close_font != null) c.TTF_CloseFont(close_font);
// Main content text
const content_surf = c.TTF_RenderText_Blended(font, "Hello from Zig & SDL3!", 0, c.SDL_Color{ .r = 255, .g = 255, .b = 255, .a = 255 });
defer if (content_surf != null) c.SDL_DestroySurface(content_surf);
const content_tex = if (content_surf != null) c.SDL_CreateTextureFromSurface(renderer, content_surf) else null;
defer if (content_tex != null) c.SDL_DestroyTexture(content_tex);
const text_w: f32 = if (content_surf != null) @floatFromInt(content_surf.?.*.w) else 0;
const text_h: f32 = if (content_surf != null) @floatFromInt(content_surf.?.*.h) else 0;
var panel = FakeWindow{};
var quit = false;
var event: c.SDL_Event = undefined;
while (!quit) {
while (c.SDL_PollEvent(&event)) {
// Escape key and Main Window X handling
if (event.type == c.SDL_EVENT_QUIT or
(event.type == c.SDL_EVENT_KEY_DOWN and event.key.key == c.SDLK_ESCAPE))
{
quit = true;
}
if (panel.handleEvent(&event)) {
quit = true;
}
}
_ = c.SDL_SetRenderDrawColor(renderer, 15, 20, 30, 255);
_ = c.SDL_RenderClear(renderer);
panel.render(renderer, font, close_font);
// Content inside panel
if (content_tex) |tex| {
const content_rect = c.SDL_FRect{
.x = panel.x + (panel.width - text_w) / 2,
.y = panel.y + 90,
.w = text_w,
.h = text_h,
};
_ = c.SDL_RenderTexture(renderer, tex, null, &content_rect);
}
_ = c.SDL_RenderPresent(renderer);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment