Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/c.zig
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ pub const SDL_StopTextInput = c_import.SDL_StopTextInput;

pub const SDL_INIT_VIDEO = c_import.SDL_INIT_VIDEO;
pub const SDL_BLENDMODE_BLEND = c_import.SDL_BLENDMODE_BLEND;
pub const SDL_WINDOW_RESIZABLE = c_import.SDL_WINDOW_RESIZABLE;
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_EVENT_WINDOW_RESIZED = c_import.SDL_EVENT_WINDOW_RESIZED;

pub const SDL_SetTextureScaleMode = c_import.SDL_SetTextureScaleMode;
pub const SDL_SCALEMODE_LINEAR = c_import.SDL_SCALEMODE_LINEAR;
Expand All @@ -50,7 +52,11 @@ 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 SDLK_LEFTBRACKET = c_import.SDLK_LEFTBRACKET;
pub const SDLK_RIGHTBRACKET = c_import.SDLK_RIGHTBRACKET;
pub const SDL_KMOD_CTRL = c_import.SDL_KMOD_CTRL;
pub const SDL_KMOD_SHIFT = c_import.SDL_KMOD_SHIFT;
pub const SDL_KMOD_GUI = c_import.SDL_KMOD_GUI;

pub const TTF_Init = c_import.TTF_Init;
pub const TTF_Quit = c_import.TTF_Quit;
Expand Down
101 changes: 80 additions & 21 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ const c = @import("c.zig");

const log = std.log.scoped(.main);

const WINDOW_WIDTH = 1200;
const WINDOW_HEIGHT = 900;
const INITIAL_WINDOW_WIDTH = 1200;
const INITIAL_WINDOW_HEIGHT = 900;
const GRID_ROWS = 3;
const GRID_COLS = 3;
const ANIMATION_DURATION_MS = 300;
Expand All @@ -32,6 +32,8 @@ const ViewMode = enum {
Expanding,
Full,
Collapsing,
PanningLeft,
PanningRight,
};

const Rect = struct {
Expand All @@ -44,6 +46,7 @@ const Rect = struct {
const AnimationState = struct {
mode: ViewMode,
focused_session: usize,
previous_session: usize,
start_time: i64,
start_rect: Rect,
target_rect: Rect,
Expand Down Expand Up @@ -253,9 +256,9 @@ pub fn main() !void {

const window = c.SDL_CreateWindow(
"Architect - Terminal Wall",
WINDOW_WIDTH,
WINDOW_HEIGHT,
0,
INITIAL_WINDOW_WIDTH,
INITIAL_WINDOW_HEIGHT,
c.SDL_WINDOW_RESIZABLE,
) orelse {
std.debug.print("SDL_CreateWindow Error: {s}\n", .{c.SDL_GetError()});
return error.WindowCreationFailed;
Expand All @@ -274,22 +277,24 @@ pub fn main() !void {
var font = try font_mod.Font.init(allocator, renderer, "/System/Library/Fonts/SFNSMono.ttf", 14);
defer font.deinit();

const full_cols = @as(u16, @intCast(@divFloor(WINDOW_WIDTH, font.cell_width)));
const full_rows = @as(u16, @intCast(@divFloor(WINDOW_HEIGHT, font.cell_height)));
const full_cols = @as(u16, @intCast(@divFloor(INITIAL_WINDOW_WIDTH, font.cell_width)));
const full_rows = @as(u16, @intCast(@divFloor(INITIAL_WINDOW_HEIGHT, font.cell_height)));
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The terminal dimensions (full_cols, full_rows) are calculated once at startup using INITIAL_WINDOW_WIDTH/HEIGHT but never recalculated when the window is resized. This means the terminal size won't match the actual window size after resizing, potentially causing rendering issues or incorrect terminal buffer sizes. These should be recalculated in the SDL_EVENT_WINDOW_RESIZED handler.

Copilot uses AI. Check for mistakes.

std.debug.print("Full window terminal size: {d}x{d}\n", .{ full_cols, full_rows });

const shell_path = std.posix.getenv("SHELL") orelse "/bin/zsh";
std.debug.print("Spawning {d} shell instances: {s}\n", .{ GRID_ROWS * GRID_COLS, shell_path });

const cell_width_pixels = WINDOW_WIDTH / GRID_COLS;
const cell_height_pixels = WINDOW_HEIGHT / GRID_ROWS;
var window_width: c_int = INITIAL_WINDOW_WIDTH;
var window_height: c_int = INITIAL_WINDOW_HEIGHT;
var cell_width_pixels = @divFloor(window_width, GRID_COLS);
var cell_height_pixels = @divFloor(window_height, GRID_ROWS);

const size = pty_mod.winsize{
.ws_row = full_rows,
.ws_col = full_cols,
.ws_xpixel = WINDOW_WIDTH,
.ws_ypixel = WINDOW_HEIGHT,
.ws_xpixel = @intCast(window_width),
.ws_ypixel = @intCast(window_height),
Comment on lines +296 to +297
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pty winsize structure is initialized once with the initial window dimensions but never updated when the window is resized. The PTY needs to be notified of size changes via ioctl(TIOCSWINSZ) in the resize handler so that terminal applications can respond to the new dimensions.

Copilot uses AI. Check for mistakes.
};

var sessions: [GRID_ROWS * GRID_COLS]SessionState = undefined;
Expand Down Expand Up @@ -324,6 +329,7 @@ pub fn main() !void {
var anim_state = AnimationState{
.mode = .Grid,
.focused_session = 0,
.previous_session = 0,
.start_time = 0,
.start_rect = Rect{ .x = 0, .y = 0, .w = 0, .h = 0 },
.target_rect = Rect{ .x = 0, .y = 0, .w = 0, .h = 0 },
Expand All @@ -338,6 +344,13 @@ pub fn main() !void {
while (c.SDL_PollEvent(&event)) {
switch (event.type) {
c.SDL_EVENT_QUIT => running = false,
c.SDL_EVENT_WINDOW_RESIZED => {
window_width = @intCast(event.window.data1);
window_height = @intCast(event.window.data2);
cell_width_pixels = @divFloor(window_width, GRID_COLS);
cell_height_pixels = @divFloor(window_height, GRID_ROWS);
Comment on lines +347 to +351
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Resize terminals when window dimensions change

The new window resize handler only updates window_width and the grid cell sizes, but the terminals/PTYs keep the startup dimensions (full_cols/full_rows) and are never resized. After shrinking the window, the renderer still assumes the original terminal width/height, so the visible area shows only the top-left portion while the shell continues writing for the larger size, causing lines to wrap off-screen and content to be truncated whenever the window is resized.

Useful? React with 👍 / 👎.

std.debug.print("Window resized to: {d}x{d}\n", .{ window_width, window_height });
},
c.SDL_EVENT_TEXT_INPUT => {
const focused = &sessions[anim_state.focused_session];
if (focused.is_scrolled) {
Expand All @@ -351,7 +364,23 @@ pub fn main() !void {
const key = event.key.key;
const mod = event.key.mod;

if (key == c.SDLK_ESCAPE and anim_state.mode == .Full) {
if ((mod & c.SDL_KMOD_GUI != 0) and (mod & c.SDL_KMOD_SHIFT != 0) and
(key == c.SDLK_RIGHTBRACKET or key == c.SDLK_LEFTBRACKET) and
anim_state.mode == .Full)
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The complex condition with multiple modifiers and key checks reduces readability. Consider extracting this into a helper function like isSwitchTerminalShortcut(mod, key) to improve code clarity and make the shortcut logic easier to maintain.

Copilot uses AI. Check for mistakes.
{
const is_next = (key == c.SDLK_RIGHTBRACKET);
const total_sessions = GRID_ROWS * GRID_COLS;
const new_session = if (is_next)
(anim_state.focused_session + 1) % total_sessions
else
(anim_state.focused_session + total_sessions - 1) % total_sessions;

anim_state.mode = if (is_next) .PanningLeft else .PanningRight;
anim_state.previous_session = anim_state.focused_session;
anim_state.focused_session = new_session;
anim_state.start_time = now;
std.debug.print("Panning to session {d} from {d}\n", .{ new_session, anim_state.previous_session });
} else 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{
Expand All @@ -363,7 +392,7 @@ pub fn main() !void {

anim_state.mode = .Collapsing;
anim_state.start_time = now;
anim_state.start_rect = Rect{ .x = 0, .y = 0, .w = WINDOW_WIDTH, .h = WINDOW_HEIGHT };
anim_state.start_rect = Rect{ .x = 0, .y = 0, .w = window_width, .h = window_height };
anim_state.target_rect = target_rect;
std.debug.print("Collapsing session: {d}\n", .{anim_state.focused_session});
} else {
Expand Down Expand Up @@ -396,7 +425,7 @@ pub fn main() !void {
.w = cell_width_pixels,
.h = cell_height_pixels,
};
const target_rect = Rect{ .x = 0, .y = 0, .w = WINDOW_WIDTH, .h = WINDOW_HEIGHT };
const target_rect = Rect{ .x = 0, .y = 0, .w = window_width, .h = window_height };

anim_state.mode = .Expanding;
anim_state.focused_session = clicked_session;
Expand All @@ -416,6 +445,8 @@ pub fn main() !void {
&anim_state,
cell_width_pixels,
cell_height_pixels,
window_width,
window_height,
);

if (hovered_session) |session_idx| {
Expand Down Expand Up @@ -448,15 +479,21 @@ pub fn main() !void {
}
}

if (anim_state.mode == .Expanding or anim_state.mode == .Collapsing) {
if (anim_state.mode == .Expanding or anim_state.mode == .Collapsing or
anim_state.mode == .PanningLeft or anim_state.mode == .PanningRight)
{
if (anim_state.isComplete(now)) {
anim_state.mode = if (anim_state.mode == .Expanding) .Full else .Grid;
anim_state.mode = switch (anim_state.mode) {
.Expanding, .PanningLeft, .PanningRight => .Full,
.Collapsing => .Grid,
else => anim_state.mode,
};
std.debug.print("Animation complete, new mode: {s}\n", .{@tagName(anim_state.mode)});
}
}

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);
try render(renderer, &sessions, allocator, cell_width_pixels, cell_height_pixels, &anim_state, now, &font, full_cols, full_rows, window_width, window_height);
_ = c.SDL_RenderPresent(renderer);
last_render = now;
}
Expand All @@ -471,17 +508,19 @@ fn calculateHoveredSession(
anim_state: *const AnimationState,
cell_width_pixels: c_int,
cell_height_pixels: c_int,
window_width: c_int,
window_height: c_int,
) ?usize {
return switch (anim_state.mode) {
.Grid => {
if (mouse_x < 0 or mouse_x >= WINDOW_WIDTH or
mouse_y < 0 or mouse_y >= WINDOW_HEIGHT) return null;
if (mouse_x < 0 or mouse_x >= window_width or
mouse_y < 0 or mouse_y >= window_height) return null;

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);
return grid_row * GRID_COLS + grid_col;
},
.Full => anim_state.focused_session,
.Full, .PanningLeft, .PanningRight => anim_state.focused_session,
.Expanding, .Collapsing => {
const rect = anim_state.getCurrentRect(std.time.milliTimestamp());
if (mouse_x >= rect.x and mouse_x < rect.x + rect.w and
Expand Down Expand Up @@ -561,6 +600,8 @@ fn render(
font: *font_mod.Font,
term_cols: u16,
term_rows: u16,
window_width: c_int,
window_height: c_int,
) RenderError!void {
// Central draw routine: depending on view mode, paint either the full grid
// or the focused session with animation-aware scaling and overlays.
Expand All @@ -584,9 +625,27 @@ fn render(
}
},
.Full => {
const full_rect = Rect{ .x = 0, .y = 0, .w = WINDOW_WIDTH, .h = WINDOW_HEIGHT };
const full_rect = Rect{ .x = 0, .y = 0, .w = window_width, .h = window_height };
try renderSession(renderer, &sessions[anim_state.focused_session], full_rect, 1.0, true, false, font, term_cols, term_rows, current_time, false);
},
.PanningLeft, .PanningRight => {
const elapsed = current_time - anim_state.start_time;
const progress = @min(1.0, @as(f32, @floatFromInt(elapsed)) / @as(f32, ANIMATION_DURATION_MS));
const eased = AnimationState.easeInOutCubic(progress);

const offset = @as(c_int, @intFromFloat(@as(f32, @floatFromInt(window_width)) * eased));
const pan_offset = if (anim_state.mode == .PanningLeft) -offset else offset;

const prev_rect = Rect{ .x = pan_offset, .y = 0, .w = window_width, .h = window_height };
try renderSession(renderer, &sessions[anim_state.previous_session], prev_rect, 1.0, false, false, font, term_cols, term_rows, current_time, false);

const new_offset = if (anim_state.mode == .PanningLeft)
window_width - offset
else
-window_width + offset;
const new_rect = Rect{ .x = new_offset, .y = 0, .w = window_width, .h = window_height };
try renderSession(renderer, &sessions[anim_state.focused_session], new_rect, 1.0, true, false, font, term_cols, term_rows, current_time, false);
},
.Expanding, .Collapsing => {
const animating_rect = anim_state.getCurrentRect(current_time);
const elapsed = current_time - anim_state.start_time;
Expand Down