diff --git a/build.zig b/build.zig index ed7cc29..535d0ca 100644 --- a/build.zig +++ b/build.zig @@ -33,14 +33,19 @@ pub fn build(b: *std.Build) void { .root_module = exe_mod, }); - exe.linkSystemLibrary("SDL2_ttf"); + exe.linkSystemLibrary("SDL3"); + exe.linkSystemLibrary("SDL3_ttf"); exe.linkLibC(); - if (std.posix.getenv("SDL2_INCLUDE_PATH")) |sdl2_include| { - exe.addIncludePath(.{ .cwd_relative = sdl2_include }); + if (std.posix.getenv("SDL3_INCLUDE_PATH")) |sdl3_include| { + exe.addIncludePath(.{ .cwd_relative = sdl3_include }); + const lib_path = b.fmt("{s}/../lib", .{sdl3_include}); + exe.addLibraryPath(.{ .cwd_relative = lib_path }); } - if (std.posix.getenv("SDL2_TTF_INCLUDE_PATH")) |sdl2_ttf_include| { - exe.addIncludePath(.{ .cwd_relative = sdl2_ttf_include }); + if (std.posix.getenv("SDL3_TTF_INCLUDE_PATH")) |sdl3_ttf_include| { + exe.addIncludePath(.{ .cwd_relative = sdl3_ttf_include }); + const ttf_lib_path = b.fmt("{s}/../lib", .{sdl3_ttf_include}); + exe.addLibraryPath(.{ .cwd_relative = ttf_lib_path }); } b.installArtifact(exe); diff --git a/flake.lock b/flake.lock index 46fa4db..1000c1a 100644 --- a/flake.lock +++ b/flake.lock @@ -68,27 +68,10 @@ "type": "github" } }, - "nixpkgs-24_05": { - "locked": { - "lastModified": 1735563628, - "narHash": "sha256-OnSAY7XDSx7CtDoqNh8jwVwh4xNL/2HaJxGjryLWzX8=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "b134951a4c9f3c995fd7be05f3243f8ecd65d798", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-24.05", - "repo": "nixpkgs", - "type": "github" - } - }, "root": { "inputs": { "flake-utils": "flake-utils", "nixpkgs": "nixpkgs", - "nixpkgs-24_05": "nixpkgs-24_05", "zig": "zig" } }, diff --git a/flake.nix b/flake.nix index 75017e9..3e37c9a 100644 --- a/flake.nix +++ b/flake.nix @@ -3,7 +3,6 @@ inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - nixpkgs-24_05.url = "github:NixOS/nixpkgs/nixos-24.05"; flake-utils.url = "github:numtide/flake-utils"; zig = { url = "github:mitchellh/zig-overlay"; @@ -11,7 +10,7 @@ }; }; - outputs = { self, nixpkgs, nixpkgs-24_05, flake-utils, zig }: + outputs = { self, nixpkgs, flake-utils, zig }: flake-utils.lib.eachDefaultSystem (system: let pkgs = import nixpkgs { @@ -20,14 +19,8 @@ allowUnfree = true; }; }; - legacy = import nixpkgs-24_05 { - inherit system; - config = { - allowUnfree = true; - }; - }; - sdl2 = legacy.SDL2; - sdl2_ttf = legacy.SDL2_ttf; + sdl3 = pkgs.sdl3; + sdl3_ttf = pkgs.sdl3-ttf; in { devShells.default = pkgs.mkShell { @@ -39,8 +32,8 @@ buildInputs = [ - sdl2.dev - sdl2_ttf + sdl3.dev + sdl3_ttf ] ++ pkgs.lib.optionals pkgs.stdenv.hostPlatform.isDarwin [ pkgs.gawk @@ -48,9 +41,9 @@ ]; shellHook = '' - export PKG_CONFIG_PATH="${sdl2}/lib/pkgconfig:${sdl2_ttf}/lib/pkgconfig:$PKG_CONFIG_PATH" - export SDL2_INCLUDE_PATH="${sdl2.dev}/include" - export SDL2_TTF_INCLUDE_PATH="${sdl2_ttf}/include" + export PKG_CONFIG_PATH="${sdl3}/lib/pkgconfig:${sdl3_ttf}/lib/pkgconfig:$PKG_CONFIG_PATH" + export SDL3_INCLUDE_PATH="${sdl3.dev}/include" + export SDL3_TTF_INCLUDE_PATH="${sdl3_ttf}/include" echo "Architect development environment" echo "Available commands: just --list" '' diff --git a/src/c.zig b/src/c.zig index c93ebaa..36e3316 100644 --- a/src/c.zig +++ b/src/c.zig @@ -1,8 +1,8 @@ // Minimal re-export layer that isolates C includes so the rest of the codebase // can `@import("c.zig")` without pulling headers repeatedly. const c_import = @cImport({ - @cInclude("SDL2/SDL.h"); - @cInclude("SDL2/SDL_ttf.h"); + @cInclude("SDL3/SDL.h"); + @cInclude("SDL3_ttf/SDL_ttf.h"); }); pub const SDL_Init = c_import.SDL_Init; @@ -15,34 +15,31 @@ pub const SDL_SetRenderDrawColor = c_import.SDL_SetRenderDrawColor; pub const SDL_RenderClear = c_import.SDL_RenderClear; pub const SDL_RenderPresent = c_import.SDL_RenderPresent; pub const SDL_RenderFillRect = c_import.SDL_RenderFillRect; -pub const SDL_RenderDrawRect = c_import.SDL_RenderDrawRect; -pub const SDL_RenderDrawLine = c_import.SDL_RenderDrawLine; -pub const SDL_RenderDrawPoint = c_import.SDL_RenderDrawPoint; -pub const SDL_RenderCopy = c_import.SDL_RenderCopy; +pub const SDL_RenderRect = c_import.SDL_RenderRect; +pub const SDL_RenderLine = c_import.SDL_RenderLine; +pub const SDL_RenderPoint = c_import.SDL_RenderPoint; +pub const SDL_RenderTexture = c_import.SDL_RenderTexture; pub const SDL_SetRenderDrawBlendMode = c_import.SDL_SetRenderDrawBlendMode; -pub const SDL_QueryTexture = c_import.SDL_QueryTexture; +pub const SDL_GetTextureSize = c_import.SDL_GetTextureSize; pub const SDL_CreateTextureFromSurface = c_import.SDL_CreateTextureFromSurface; pub const SDL_DestroyTexture = c_import.SDL_DestroyTexture; -pub const SDL_FreeSurface = c_import.SDL_FreeSurface; +pub const SDL_DestroySurface = c_import.SDL_DestroySurface; pub const SDL_GetError = c_import.SDL_GetError; pub const SDL_PollEvent = c_import.SDL_PollEvent; pub const SDL_Delay = c_import.SDL_Delay; +pub const SDL_StartTextInput = c_import.SDL_StartTextInput; +pub const SDL_StopTextInput = c_import.SDL_StopTextInput; pub const SDL_INIT_VIDEO = c_import.SDL_INIT_VIDEO; -pub const SDL_WINDOW_SHOWN = c_import.SDL_WINDOW_SHOWN; -pub const SDL_WINDOWPOS_CENTERED = c_import.SDL_WINDOWPOS_CENTERED; -pub const SDL_RENDERER_ACCELERATED = c_import.SDL_RENDERER_ACCELERATED; pub const SDL_BLENDMODE_BLEND = c_import.SDL_BLENDMODE_BLEND; -pub const SDL_QUIT = c_import.SDL_QUIT; -pub const SDL_KEYDOWN = c_import.SDL_KEYDOWN; -pub const SDL_TEXTINPUT = c_import.SDL_TEXTINPUT; -pub const SDL_MOUSEBUTTONDOWN = c_import.SDL_MOUSEBUTTONDOWN; -pub const SDL_MOUSEWHEEL = c_import.SDL_MOUSEWHEEL; +pub const SDL_EVENT_QUIT = c_import.SDL_EVENT_QUIT; +pub const SDL_EVENT_KEY_DOWN = c_import.SDL_EVENT_KEY_DOWN; +pub const SDL_EVENT_TEXT_INPUT = c_import.SDL_EVENT_TEXT_INPUT; +pub const SDL_EVENT_MOUSE_BUTTON_DOWN = c_import.SDL_EVENT_MOUSE_BUTTON_DOWN; +pub const SDL_EVENT_MOUSE_WHEEL = c_import.SDL_EVENT_MOUSE_WHEEL; -pub const SDL_HINT_RENDER_SCALE_QUALITY = c_import.SDL_HINT_RENDER_SCALE_QUALITY; -pub const SDL_SetHint = c_import.SDL_SetHint; pub const SDL_SetTextureScaleMode = c_import.SDL_SetTextureScaleMode; -pub const SDL_ScaleModeLinear = c_import.SDL_ScaleModeLinear; +pub const SDL_SCALEMODE_LINEAR = c_import.SDL_SCALEMODE_LINEAR; pub const SDLK_ESCAPE = c_import.SDLK_ESCAPE; pub const SDLK_RETURN = c_import.SDLK_RETURN; @@ -51,9 +48,9 @@ pub const SDLK_UP = c_import.SDLK_UP; pub const SDLK_DOWN = c_import.SDLK_DOWN; pub const SDLK_LEFT = c_import.SDLK_LEFT; pub const SDLK_RIGHT = c_import.SDLK_RIGHT; -pub const SDLK_a = c_import.SDLK_a; -pub const SDLK_z = c_import.SDLK_z; -pub const KMOD_CTRL = c_import.KMOD_CTRL; +pub const SDLK_A = c_import.SDLK_A; +pub const SDLK_Z = c_import.SDLK_Z; +pub const SDL_KMOD_CTRL = c_import.SDL_KMOD_CTRL; pub const TTF_Init = c_import.TTF_Init; pub const TTF_Quit = c_import.TTF_Quit; @@ -61,15 +58,17 @@ pub const TTF_OpenFont = c_import.TTF_OpenFont; pub const TTF_CloseFont = c_import.TTF_CloseFont; pub const TTF_RenderText_Blended = c_import.TTF_RenderText_Blended; pub const TTF_RenderGlyph_Blended = c_import.TTF_RenderGlyph_Blended; -pub const TTF_SizeText = c_import.TTF_SizeText; -pub const TTF_GetError = c_import.TTF_GetError; +pub const TTF_GetStringSize = c_import.TTF_GetStringSize; pub const SDL_Event = c_import.SDL_Event; +pub const SDL_FRect = c_import.SDL_FRect; pub const SDL_Rect = c_import.SDL_Rect; +pub const SDL_FColor = c_import.SDL_FColor; pub const SDL_Color = c_import.SDL_Color; pub const SDL_Renderer = c_import.SDL_Renderer; pub const SDL_Window = c_import.SDL_Window; pub const SDL_Texture = c_import.SDL_Texture; pub const SDL_Surface = c_import.SDL_Surface; -pub const SDL_Keysym = c_import.SDL_Keysym; +pub const SDL_Keycode = c_import.SDL_Keycode; +pub const SDL_Keymod = c_import.SDL_Keymod; pub const TTF_Font = c_import.TTF_Font; diff --git a/src/font.zig b/src/font.zig index 764d49b..821ce56 100644 --- a/src/font.zig +++ b/src/font.zig @@ -23,14 +23,20 @@ pub const Font = struct { } || std.mem.Allocator.Error; pub fn init(allocator: std.mem.Allocator, renderer: *c.SDL_Renderer, font_path: [*:0]const u8, size: c_int) InitError!Font { - const font = c.TTF_OpenFont(font_path, size) orelse { - log.err("TTF_OpenFont failed: {s}", .{c.TTF_GetError()}); + const font = c.TTF_OpenFont(font_path, @floatFromInt(size)) orelse { + log.err("TTF_OpenFont failed: {s}", .{c.SDL_GetError()}); return error.FontLoadFailed; }; var cell_width: c_int = 0; var cell_height: c_int = 0; - _ = c.TTF_SizeText(font, "M", &cell_width, &cell_height); + if (!c.TTF_GetStringSize(font, "M", 1, &cell_width, &cell_height)) { + log.err("TTF_GetStringSize failed: {s}", .{c.SDL_GetError()}); + c.TTF_CloseFont(font); + return error.FontLoadFailed; + } + + log.debug("Font cell dimensions: {d}x{d}", .{ cell_width, cell_height }); return Font{ .font = font, @@ -67,18 +73,18 @@ pub const Font = struct { return err; }; - var tex_width: c_int = 0; - var tex_height: c_int = 0; - _ = c.SDL_QueryTexture(texture, null, null, &tex_width, &tex_height); + var tex_width_f: f32 = 0; + var tex_height_f: f32 = 0; + _ = c.SDL_GetTextureSize(texture, &tex_width_f, &tex_height_f); - const dest_rect = c.SDL_Rect{ - .x = x, - .y = y, - .w = target_width, - .h = target_height, + const dest_rect = c.SDL_FRect{ + .x = @floatFromInt(x), + .y = @floatFromInt(y), + .w = @floatFromInt(target_width), + .h = @floatFromInt(target_height), }; - _ = c.SDL_RenderCopy(self.renderer, texture, null, &dest_rect); + _ = c.SDL_RenderTexture(self.renderer, texture, null, &dest_rect); } fn getGlyphTexture(self: *Font, codepoint: u21, fg_color: c.SDL_Color) RenderGlyphError!*c.SDL_Texture { @@ -95,7 +101,7 @@ pub const Font = struct { // others), then keep a texture keyed by codepoint+color for reuse. const surface = if (codepoint < 0x10000) blk: { break :blk c.TTF_RenderGlyph_Blended(self.font, @intCast(codepoint), fg_color) orelse { - log.debug("TTF_RenderGlyph_Blended failed for U+{X:0>4}: {s}", .{ codepoint, c.TTF_GetError() }); + log.debug("TTF_RenderGlyph_Blended failed for U+{X:0>4}: {s}", .{ codepoint, c.SDL_GetError() }); return error.GlyphRenderFailed; }; } else blk: { @@ -106,19 +112,19 @@ pub const Font = struct { @memcpy(text_buf[0..len], utf8_buf[0..len]); text_buf[len] = 0; - break :blk c.TTF_RenderText_Blended(self.font, @ptrCast(&text_buf), fg_color) orelse { - log.debug("TTF_RenderText_Blended failed for U+{X:0>4}: {s}", .{ codepoint, c.TTF_GetError() }); + break :blk c.TTF_RenderText_Blended(self.font, @ptrCast(&text_buf), len, fg_color) orelse { + log.debug("TTF_RenderText_Blended failed for U+{X:0>4}: {s}", .{ codepoint, c.SDL_GetError() }); return error.GlyphRenderFailed; }; }; - defer c.SDL_FreeSurface(surface); + defer c.SDL_DestroySurface(surface); const texture = c.SDL_CreateTextureFromSurface(self.renderer, surface) orelse { log.err("SDL_CreateTextureFromSurface failed: {s}", .{c.SDL_GetError()}); return error.TextureCreationFailed; }; - _ = c.SDL_SetTextureScaleMode(texture, c.SDL_ScaleModeLinear); + _ = c.SDL_SetTextureScaleMode(texture, c.SDL_SCALEMODE_LINEAR); try self.glyph_cache.put(key, texture); return texture; diff --git a/src/main.zig b/src/main.zig index 879c959..6c8376f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -239,34 +239,33 @@ pub fn main() !void { const notify_thread = try startNotifyThread(allocator, notify_sock, ¬ify_queue); notify_thread.detach(); - _ = c.SDL_SetHint(c.SDL_HINT_RENDER_SCALE_QUALITY, "1"); - - if (c.SDL_Init(c.SDL_INIT_VIDEO) != 0) { + if (!c.SDL_Init(c.SDL_INIT_VIDEO)) { std.debug.print("SDL_Init Error: {s}\n", .{c.SDL_GetError()}); return error.SDLInitFailed; } defer c.SDL_Quit(); - if (c.TTF_Init() != 0) { - std.debug.print("TTF_Init Error: {s}\n", .{c.TTF_GetError()}); + if (!c.TTF_Init()) { + std.debug.print("TTF_Init Error: {s}\n", .{c.SDL_GetError()}); return error.TTFInitFailed; } defer c.TTF_Quit(); const window = c.SDL_CreateWindow( "Architect - Terminal Wall", - c.SDL_WINDOWPOS_CENTERED, - c.SDL_WINDOWPOS_CENTERED, WINDOW_WIDTH, WINDOW_HEIGHT, - c.SDL_WINDOW_SHOWN, + 0, ) orelse { std.debug.print("SDL_CreateWindow Error: {s}\n", .{c.SDL_GetError()}); return error.WindowCreationFailed; }; defer c.SDL_DestroyWindow(window); - const renderer = c.SDL_CreateRenderer(window, -1, c.SDL_RENDERER_ACCELERATED) orelse { + _ = c.SDL_StartTextInput(window); + defer _ = c.SDL_StopTextInput(window); + + const renderer = c.SDL_CreateRenderer(window, null) orelse { std.debug.print("SDL_CreateRenderer Error: {s}\n", .{c.SDL_GetError()}); return error.RendererCreationFailed; }; @@ -336,22 +335,23 @@ pub fn main() !void { const now = std.time.milliTimestamp(); var event: c.SDL_Event = undefined; - while (c.SDL_PollEvent(&event) != 0) { + while (c.SDL_PollEvent(&event)) { switch (event.type) { - c.SDL_QUIT => running = false, - c.SDL_TEXTINPUT => { + c.SDL_EVENT_QUIT => running = false, + c.SDL_EVENT_TEXT_INPUT => { const focused = &sessions[anim_state.focused_session]; if (focused.is_scrolled) { focused.terminal.screens.active.pages.scroll(.{ .active = {} }); focused.is_scrolled = false; } - const text = std.mem.sliceTo(&event.text.text, 0); + const text = std.mem.sliceTo(event.text.text, 0); _ = try focused.shell.write(text); }, - c.SDL_KEYDOWN => { - const key = event.key.keysym; + c.SDL_EVENT_KEY_DOWN => { + const key = event.key.key; + const mod = event.key.mod; - if (key.sym == c.SDLK_ESCAPE and anim_state.mode == .Full) { + if (key == c.SDLK_ESCAPE and anim_state.mode == .Full) { const grid_row: c_int = @intCast(anim_state.focused_session / GRID_COLS); const grid_col: c_int = @intCast(anim_state.focused_session % GRID_COLS); const target_rect = Rect{ @@ -373,16 +373,16 @@ pub fn main() !void { focused.is_scrolled = false; } var buf: [8]u8 = undefined; - const n = encodeKey(key, &buf); + const n = encodeKeyWithMod(key, mod, &buf); if (n > 0) { _ = try focused.shell.write(buf[0..n]); } } }, - c.SDL_MOUSEBUTTONDOWN => { + c.SDL_EVENT_MOUSE_BUTTON_DOWN => { if (anim_state.mode == .Grid) { - const mouse_x = event.button.x; - const mouse_y = event.button.y; + const mouse_x: c_int = @intFromFloat(event.button.x); + const mouse_y: c_int = @intFromFloat(event.button.y); const grid_col = @min(@as(usize, @intCast(@divFloor(mouse_x, cell_width_pixels))), GRID_COLS - 1); const grid_row = @min(@as(usize, @intCast(@divFloor(mouse_y, cell_height_pixels))), GRID_ROWS - 1); const clicked_session: usize = grid_row * @as(usize, GRID_COLS) + grid_col; @@ -406,9 +406,9 @@ pub fn main() !void { std.debug.print("Expanding session: {d}\n", .{clicked_session}); } }, - c.SDL_MOUSEWHEEL => { - const mouse_x = event.wheel.mouseX; - const mouse_y = event.wheel.mouseY; + c.SDL_EVENT_MOUSE_WHEEL => { + const mouse_x: c_int = @intFromFloat(event.wheel.mouse_x); + const mouse_y: c_int = @intFromFloat(event.wheel.mouse_y); const hovered_session = calculateHoveredSession( mouse_x, @@ -419,7 +419,7 @@ pub fn main() !void { ); if (hovered_session) |session_idx| { - const raw_delta = event.wheel.preciseY; + const raw_delta = event.wheel.y; const scroll_delta = -@as(isize, @intFromFloat(raw_delta * @as(f32, @floatFromInt(SCROLL_LINES_PER_TICK)))); if (scroll_delta != 0) { scrollSession(&sessions[session_idx], scroll_delta); @@ -457,7 +457,7 @@ pub fn main() !void { if (now - last_render >= render_interval_ms) { try render(renderer, &sessions, allocator, cell_width_pixels, cell_height_pixels, &anim_state, now, &font, full_cols, full_rows); - c.SDL_RenderPresent(renderer); + _ = c.SDL_RenderPresent(renderer); last_render = now; } @@ -640,11 +640,11 @@ fn renderSession( } else { _ = c.SDL_SetRenderDrawColor(renderer, 20, 20, 20, 255); } - const bg_rect = c.SDL_Rect{ - .x = rect.x, - .y = rect.y, - .w = rect.w, - .h = rect.h, + const bg_rect = c.SDL_FRect{ + .x = @floatFromInt(rect.x), + .y = @floatFromInt(rect.y), + .w = @floatFromInt(rect.w), + .h = @floatFromInt(rect.h), }; _ = c.SDL_RenderFillRect(renderer, &bg_rect); @@ -706,22 +706,22 @@ fn renderSession( } else { _ = c.SDL_SetRenderDrawColor(renderer, 60, 60, 60, 255); } - const border_rect = c.SDL_Rect{ - .x = rect.x, - .y = rect.y, - .w = rect.w, - .h = rect.h, + const border_rect = c.SDL_FRect{ + .x = @floatFromInt(rect.x), + .y = @floatFromInt(rect.y), + .w = @floatFromInt(rect.w), + .h = @floatFromInt(rect.h), }; - _ = c.SDL_RenderDrawRect(renderer, &border_rect); + _ = c.SDL_RenderRect(renderer, &border_rect); } if (is_grid_view and session.is_scrolled) { _ = c.SDL_SetRenderDrawColor(renderer, 255, 255, 100, 200); - const indicator_rect = c.SDL_Rect{ - .x = rect.x, - .y = rect.y + rect.h - 4, - .w = rect.w, - .h = 4, + const indicator_rect = c.SDL_FRect{ + .x = @floatFromInt(rect.x), + .y = @floatFromInt(rect.y + rect.h - 4), + .w = @floatFromInt(rect.w), + .h = 4.0, }; _ = c.SDL_RenderFillRect(renderer, &indicator_rect); } @@ -748,11 +748,11 @@ fn renderSession( }; _ = c.SDL_SetRenderDrawBlendMode(renderer, c.SDL_BLENDMODE_BLEND); _ = c.SDL_SetRenderDrawColor(renderer, tint_color.r, tint_color.g, tint_color.b, tint_color.a); - const tint_rect = c.SDL_Rect{ - .x = rect.x, - .y = rect.y, - .w = rect.w, - .h = rect.h, + const tint_rect = c.SDL_FRect{ + .x = @floatFromInt(rect.x), + .y = @floatFromInt(rect.y), + .w = @floatFromInt(rect.w), + .h = @floatFromInt(rect.h), }; _ = c.SDL_RenderFillRect(renderer, &tint_rect); } @@ -763,11 +763,11 @@ fn applyTvOverlay(renderer: *c.SDL_Renderer, rect: Rect, is_focused: bool) void // Subtle vignette across the panel _ = c.SDL_SetRenderDrawColor(renderer, 0, 0, 0, 60); - _ = c.SDL_RenderFillRect(renderer, &c.SDL_Rect{ - .x = rect.x, - .y = rect.y, - .w = rect.w, - .h = rect.h, + _ = c.SDL_RenderFillRect(renderer, &c.SDL_FRect{ + .x = @floatFromInt(rect.x), + .y = @floatFromInt(rect.y), + .w = @floatFromInt(rect.w), + .h = @floatFromInt(rect.h), }); const radius: c_int = 12; @@ -782,28 +782,34 @@ fn applyTvOverlay(renderer: *c.SDL_Renderer, rect: Rect, is_focused: bool) void } fn drawRoundedBorder(renderer: *c.SDL_Renderer, rect: Rect, radius: c_int) void { + const fx = @as(f32, @floatFromInt(rect.x)); + const fy = @as(f32, @floatFromInt(rect.y)); + const fw = @as(f32, @floatFromInt(rect.w)); + const fh = @as(f32, @floatFromInt(rect.h)); + const frad = @as(f32, @floatFromInt(radius)); + // Straight edges - _ = c.SDL_RenderDrawLine(renderer, rect.x + radius, rect.y, rect.x + rect.w - radius - 1, rect.y); - _ = c.SDL_RenderDrawLine(renderer, rect.x + radius, rect.y + rect.h - 1, rect.x + rect.w - radius - 1, rect.y + rect.h - 1); - _ = c.SDL_RenderDrawLine(renderer, rect.x, rect.y + radius, rect.x, rect.y + rect.h - radius - 1); - _ = c.SDL_RenderDrawLine(renderer, rect.x + rect.w - 1, rect.y + radius, rect.x + rect.w - 1, rect.y + rect.h - radius - 1); + _ = c.SDL_RenderLine(renderer, fx + frad, fy, fx + fw - frad - 1.0, fy); + _ = c.SDL_RenderLine(renderer, fx + frad, fy + fh - 1.0, fx + fw - frad - 1.0, fy + fh - 1.0); + _ = c.SDL_RenderLine(renderer, fx, fy + frad, fx, fy + fh - frad - 1.0); + _ = c.SDL_RenderLine(renderer, fx + fw - 1.0, fy + frad, fx + fw - 1.0, fy + fh - frad - 1.0); // Corners (quarter circles) var angle: f32 = 0.0; const step: f32 = std.math.pi / 64.0; while (angle <= std.math.pi / 2.0) : (angle += step) { - const rx: c_int = @intFromFloat(@round(@as(f32, @floatFromInt(radius)) * std.math.cos(angle))); - const ry: c_int = @intFromFloat(@round(@as(f32, @floatFromInt(radius)) * std.math.sin(angle))); - - const centers = [_]struct { x: c_int, y: c_int, sx: c_int, sy: c_int }{ - .{ .x = rect.x + radius, .y = rect.y + radius, .sx = -1, .sy = -1 }, // top-left - .{ .x = rect.x + rect.w - radius - 1, .y = rect.y + radius, .sx = 1, .sy = -1 }, // top-right - .{ .x = rect.x + radius, .y = rect.y + rect.h - radius - 1, .sx = -1, .sy = 1 }, // bottom-left - .{ .x = rect.x + rect.w - radius - 1, .y = rect.y + rect.h - radius - 1, .sx = 1, .sy = 1 }, // bottom-right + const rx = frad * std.math.cos(angle); + const ry = frad * std.math.sin(angle); + + const centers = [_]struct { x: f32, y: f32, sx: f32, sy: f32 }{ + .{ .x = fx + frad, .y = fy + frad, .sx = -1.0, .sy = -1.0 }, // top-left + .{ .x = fx + fw - frad - 1.0, .y = fy + frad, .sx = 1.0, .sy = -1.0 }, // top-right + .{ .x = fx + frad, .y = fy + fh - frad - 1.0, .sx = -1.0, .sy = 1.0 }, // bottom-left + .{ .x = fx + fw - frad - 1.0, .y = fy + fh - frad - 1.0, .sx = 1.0, .sy = 1.0 }, // bottom-right }; for (centers) |cinfo| { - _ = c.SDL_RenderDrawPoint(renderer, cinfo.x + cinfo.sx * rx, cinfo.y + cinfo.sy * ry); + _ = c.SDL_RenderPoint(renderer, cinfo.x + cinfo.sx * rx, cinfo.y + cinfo.sy * ry); } } } @@ -824,18 +830,15 @@ fn drawThickBorder(renderer: *c.SDL_Renderer, rect: Rect, thickness: c_int, colo } } -fn encodeKey(key: c.SDL_Keysym, buf: []u8) usize { - const sym = key.sym; - - // Minimal key → ANSI encoding for terminals; return 0 when unhandled. - if (key.mod & c.KMOD_CTRL != 0) { - if (sym >= c.SDLK_a and sym <= c.SDLK_z) { - buf[0] = @as(u8, @intCast(sym - c.SDLK_a + 1)); +fn encodeKeyWithMod(key: c.SDL_Keycode, mod: c.SDL_Keymod, buf: []u8) usize { + if (mod & c.SDL_KMOD_CTRL != 0) { + if (key >= c.SDLK_A and key <= c.SDLK_Z) { + buf[0] = @as(u8, @intCast(key - c.SDLK_A + 1)); return 1; } } - return switch (sym) { + return switch (key) { c.SDLK_RETURN => blk: { buf[0] = '\r'; break :blk 1; @@ -985,50 +988,43 @@ fn startNotifyThread( return try std.Thread.spawn(.{}, handler.run, .{ctx}); } -test "encodeKey - return key" { +test "encodeKeyWithMod - return key" { var buf: [8]u8 = undefined; - const key = c.SDL_Keysym{ .sym = c.SDLK_RETURN, .mod = 0, .scancode = 0, .unused = 0 }; - const n = encodeKey(key, &buf); + const n = encodeKeyWithMod(c.SDLK_RETURN, 0, &buf); try std.testing.expectEqual(@as(usize, 1), n); try std.testing.expectEqual(@as(u8, '\r'), buf[0]); } -test "encodeKey - arrow keys" { +test "encodeKeyWithMod - arrow keys" { var buf: [8]u8 = undefined; - const up = c.SDL_Keysym{ .sym = c.SDLK_UP, .mod = 0, .scancode = 0, .unused = 0 }; - const n_up = encodeKey(up, &buf); + const n_up = encodeKeyWithMod(c.SDLK_UP, 0, &buf); try std.testing.expectEqual(@as(usize, 3), n_up); try std.testing.expectEqualSlices(u8, "\x1b[A", buf[0..n_up]); - const down = c.SDL_Keysym{ .sym = c.SDLK_DOWN, .mod = 0, .scancode = 0, .unused = 0 }; - const n_down = encodeKey(down, &buf); + const n_down = encodeKeyWithMod(c.SDLK_DOWN, 0, &buf); try std.testing.expectEqual(@as(usize, 3), n_down); try std.testing.expectEqualSlices(u8, "\x1b[B", buf[0..n_down]); - const right = c.SDL_Keysym{ .sym = c.SDLK_RIGHT, .mod = 0, .scancode = 0, .unused = 0 }; - const n_right = encodeKey(right, &buf); + const n_right = encodeKeyWithMod(c.SDLK_RIGHT, 0, &buf); try std.testing.expectEqual(@as(usize, 3), n_right); try std.testing.expectEqualSlices(u8, "\x1b[C", buf[0..n_right]); - const left = c.SDL_Keysym{ .sym = c.SDLK_LEFT, .mod = 0, .scancode = 0, .unused = 0 }; - const n_left = encodeKey(left, &buf); + const n_left = encodeKeyWithMod(c.SDLK_LEFT, 0, &buf); try std.testing.expectEqual(@as(usize, 3), n_left); try std.testing.expectEqualSlices(u8, "\x1b[D", buf[0..n_left]); } -test "encodeKey - ctrl+a" { +test "encodeKeyWithMod - ctrl+a" { var buf: [8]u8 = undefined; - const key = c.SDL_Keysym{ .sym = c.SDLK_a, .mod = c.KMOD_CTRL, .scancode = 0, .unused = 0 }; - const n = encodeKey(key, &buf); + const n = encodeKeyWithMod(c.SDLK_A, c.SDL_KMOD_CTRL, &buf); try std.testing.expectEqual(@as(usize, 1), n); try std.testing.expectEqual(@as(u8, 1), buf[0]); } -test "encodeKey - unknown key" { +test "encodeKeyWithMod - unknown key" { var buf: [8]u8 = undefined; - const key = c.SDL_Keysym{ .sym = 0, .mod = 0, .scancode = 0, .unused = 0 }; - const n = encodeKey(key, &buf); + const n = encodeKeyWithMod(0, 0, &buf); try std.testing.expectEqual(@as(usize, 0), n); }