@@ -17,6 +17,59 @@ const GRID_ROWS = 3;
1717const GRID_COLS = 3 ;
1818const COLS = 40 ;
1919const ROWS = 12 ;
20+ const ANIMATION_DURATION_MS = 300 ;
21+
22+ const ViewMode = enum {
23+ Grid ,
24+ Expanding ,
25+ Full ,
26+ Collapsing ,
27+ };
28+
29+ const Rect = struct {
30+ x : c_int ,
31+ y : c_int ,
32+ w : c_int ,
33+ h : c_int ,
34+ };
35+
36+ const AnimationState = struct {
37+ mode : ViewMode ,
38+ focused_session : usize ,
39+ start_time : i64 ,
40+ start_rect : Rect ,
41+ target_rect : Rect ,
42+
43+ fn easeInOutCubic (t : f32 ) f32 {
44+ if (t < 0.5 ) {
45+ return 4 * t * t * t ;
46+ } else {
47+ const p = 2 * t - 2 ;
48+ return 1 + p * p * p / 2 ;
49+ }
50+ }
51+
52+ fn interpolateRect (start : Rect , target : Rect , progress : f32 ) Rect {
53+ const eased = easeInOutCubic (progress );
54+ return Rect {
55+ .x = start .x + @as (c_int , @intFromFloat (@as (f32 , @floatFromInt (target .x - start .x )) * eased )),
56+ .y = start .y + @as (c_int , @intFromFloat (@as (f32 , @floatFromInt (target .y - start .y )) * eased )),
57+ .w = start .w + @as (c_int , @intFromFloat (@as (f32 , @floatFromInt (target .w - start .w )) * eased )),
58+ .h = start .h + @as (c_int , @intFromFloat (@as (f32 , @floatFromInt (target .h - start .h )) * eased )),
59+ };
60+ }
61+
62+ fn getCurrentRect (self : * const AnimationState , current_time : i64 ) Rect {
63+ const elapsed = current_time - self .start_time ;
64+ const progress = @min (1.0 , @as (f32 , @floatFromInt (elapsed )) / @as (f32 , ANIMATION_DURATION_MS ));
65+ return interpolateRect (self .start_rect , self .target_rect , progress );
66+ }
67+
68+ fn isComplete (self : * const AnimationState , current_time : i64 ) bool {
69+ const elapsed = current_time - self .start_time ;
70+ return elapsed >= ANIMATION_DURATION_MS ;
71+ }
72+ };
2073
2174const VtStreamType = blk : {
2275 const T = ghostty_vt .Terminal ;
@@ -44,7 +97,8 @@ const SessionState = struct {
4497 });
4598 errdefer terminal .deinit (allocator );
4699
47- const stream = terminal .vtStream ();
100+ var stream = terminal .vtStream ();
101+ errdefer stream .deinit ();
48102
49103 try makeNonBlocking (shell .pty .master );
50104
@@ -139,28 +193,71 @@ pub fn main() !void {
139193 var running = true ;
140194 var last_render : i64 = 0 ;
141195 const render_interval_ms : i64 = 16 ;
142- var focused_session : usize = 0 ;
196+
197+ var anim_state = AnimationState {
198+ .mode = .Grid ,
199+ .focused_session = 0 ,
200+ .start_time = 0 ,
201+ .start_rect = Rect { .x = 0 , .y = 0 , .w = 0 , .h = 0 },
202+ .target_rect = Rect { .x = 0 , .y = 0 , .w = 0 , .h = 0 },
203+ };
143204
144205 while (running ) {
206+ const now = std .time .milliTimestamp ();
207+
145208 var event : c.SDL_Event = undefined ;
146209 while (c .SDL_PollEvent (& event ) != 0 ) {
147210 switch (event .type ) {
148211 c .SDL_QUIT = > running = false ,
149212 c .SDL_KEYDOWN = > {
150213 const key = event .key .keysym ;
151- var buf : [8 ]u8 = undefined ;
152- const n = try encodeKey (key , & buf );
153- if (n > 0 ) {
154- _ = try sessions [focused_session ].shell .write (buf [0.. n ]);
214+
215+ if (key .sym == c .SDLK_ESCAPE and anim_state .mode == .Full ) {
216+ const grid_row : c_int = @intCast (anim_state .focused_session / GRID_COLS );
217+ const grid_col : c_int = @intCast (anim_state .focused_session % GRID_COLS );
218+ const target_rect = Rect {
219+ .x = grid_col * cell_width_pixels ,
220+ .y = grid_row * cell_height_pixels ,
221+ .w = cell_width_pixels ,
222+ .h = cell_height_pixels ,
223+ };
224+
225+ anim_state .mode = .Collapsing ;
226+ anim_state .start_time = now ;
227+ anim_state .start_rect = Rect { .x = 0 , .y = 0 , .w = WINDOW_WIDTH , .h = WINDOW_HEIGHT };
228+ anim_state .target_rect = target_rect ;
229+ std .debug .print ("Collapsing session: {d}\n " , .{anim_state .focused_session });
230+ } else {
231+ var buf : [8 ]u8 = undefined ;
232+ const n = try encodeKey (key , & buf );
233+ if (n > 0 ) {
234+ _ = try sessions [anim_state .focused_session ].shell .write (buf [0.. n ]);
235+ }
155236 }
156237 },
157238 c .SDL_MOUSEBUTTONDOWN = > {
158- const mouse_x = event .button .x ;
159- const mouse_y = event .button .y ;
160- const grid_col = @min (@as (usize , @intCast (@divFloor (mouse_x , cell_width_pixels ))), GRID_COLS - 1 );
161- const grid_row = @min (@as (usize , @intCast (@divFloor (mouse_y , cell_height_pixels ))), GRID_ROWS - 1 );
162- focused_session = grid_row * GRID_COLS + grid_col ;
163- std .debug .print ("Focused session: {d}\n " , .{focused_session });
239+ if (anim_state .mode == .Grid ) {
240+ const mouse_x = event .button .x ;
241+ const mouse_y = event .button .y ;
242+ const grid_col = @min (@as (usize , @intCast (@divFloor (mouse_x , cell_width_pixels ))), GRID_COLS - 1 );
243+ const grid_row = @min (@as (usize , @intCast (@divFloor (mouse_y , cell_height_pixels ))), GRID_ROWS - 1 );
244+ const clicked_session = grid_row * GRID_COLS + grid_col ;
245+
246+ const start_rect = Rect {
247+ .x = @as (c_int , @intCast (grid_col )) * cell_width_pixels ,
248+ .y = @as (c_int , @intCast (grid_row )) * cell_height_pixels ,
249+ .w = cell_width_pixels ,
250+ .h = cell_height_pixels ,
251+ };
252+ const target_rect = Rect { .x = 0 , .y = 0 , .w = WINDOW_WIDTH , .h = WINDOW_HEIGHT };
253+
254+ anim_state .mode = .Expanding ;
255+ anim_state .focused_session = clicked_session ;
256+ anim_state .start_time = now ;
257+ anim_state .start_rect = start_rect ;
258+ anim_state .target_rect = target_rect ;
259+ std .debug .print ("Expanding session: {d}\n " , .{clicked_session });
260+ }
164261 },
165262 else = > {},
166263 }
@@ -170,9 +267,15 @@ pub fn main() !void {
170267 try session .processOutput ();
171268 }
172269
173- const now = std .time .milliTimestamp ();
270+ if (anim_state .mode == .Expanding or anim_state .mode == .Collapsing ) {
271+ if (anim_state .isComplete (now )) {
272+ anim_state .mode = if (anim_state .mode == .Expanding ) .Full else .Grid ;
273+ std .debug .print ("Animation complete, new mode: {s}\n " , .{@tagName (anim_state .mode )});
274+ }
275+ }
276+
174277 if (now - last_render >= render_interval_ms ) {
175- try renderGrid (renderer , & sessions , allocator , cell_width_pixels , cell_height_pixels , focused_session );
278+ try render (renderer , & sessions , allocator , cell_width_pixels , cell_height_pixels , & anim_state , now );
176279 c .SDL_RenderPresent (renderer );
177280 last_render = now ;
178281 }
@@ -181,81 +284,123 @@ pub fn main() !void {
181284 }
182285}
183286
184- fn renderGrid (
287+ fn render (
185288 renderer : * c.SDL_Renderer ,
186289 sessions : []SessionState ,
187290 _ : std.mem.Allocator ,
188291 cell_width_pixels : c_int ,
189292 cell_height_pixels : c_int ,
190- focused_session : usize ,
293+ anim_state : * const AnimationState ,
294+ current_time : i64 ,
191295) ! void {
192296 _ = c .SDL_SetRenderDrawColor (renderer , 0 , 0 , 0 , 255 );
193297 _ = c .SDL_RenderClear (renderer );
194298
195- for (sessions , 0.. ) | * session , i | {
196- const grid_row : c_int = @intCast (i / GRID_COLS );
197- const grid_col : c_int = @intCast (i % GRID_COLS );
198-
199- const cell_x : c_int = grid_col * cell_width_pixels ;
200- const cell_y : c_int = grid_row * cell_height_pixels ;
201-
202- const is_focused = (i == focused_session );
203- if (is_focused ) {
204- _ = c .SDL_SetRenderDrawColor (renderer , 40 , 40 , 60 , 255 );
205- } else {
206- _ = c .SDL_SetRenderDrawColor (renderer , 20 , 20 , 20 , 255 );
207- }
208- const bg_rect = c.SDL_Rect {
209- .x = cell_x ,
210- .y = cell_y ,
211- .w = cell_width_pixels ,
212- .h = cell_height_pixels ,
213- };
214- _ = c .SDL_RenderFillRect (renderer , & bg_rect );
215-
216- const screen = & session .terminal .screen ;
217- const pages = screen .pages ;
218-
219- var row : usize = 0 ;
220- while (row < ROWS ) : (row += 1 ) {
221- var col : usize = 0 ;
222- while (col < COLS ) : (col += 1 ) {
223- const list_cell = pages .getCell (.{ .active = .{
224- .x = @intCast (col ),
225- .y = @intCast (row ),
226- } }) orelse continue ;
227-
228- const cell = list_cell .cell ;
229- const cp = cell .content .codepoint ;
230- if (cp == 0 or cp == ' ' ) continue ;
231-
232- const x : c_int = cell_x + @as (c_int , @intCast (col * CELL_WIDTH ));
233- const y : c_int = cell_y + @as (c_int , @intCast (row * CELL_HEIGHT ));
234-
235- _ = c .SDL_SetRenderDrawColor (renderer , 200 , 200 , 200 , 255 );
236- const rect = c.SDL_Rect {
237- .x = x ,
238- .y = y ,
239- .w = CELL_WIDTH ,
240- .h = CELL_HEIGHT ,
299+ switch (anim_state .mode ) {
300+ .Grid = > {
301+ for (sessions , 0.. ) | * session , i | {
302+ const grid_row : c_int = @intCast (i / GRID_COLS );
303+ const grid_col : c_int = @intCast (i % GRID_COLS );
304+
305+ const cell_rect = Rect {
306+ .x = grid_col * cell_width_pixels ,
307+ .y = grid_row * cell_height_pixels ,
308+ .w = cell_width_pixels ,
309+ .h = cell_height_pixels ,
241310 };
242- _ = c .SDL_RenderFillRect (renderer , & rect );
311+
312+ try renderSession (renderer , session , cell_rect , i == anim_state .focused_session );
313+ }
314+ },
315+ .Full = > {
316+ const full_rect = Rect { .x = 0 , .y = 0 , .w = WINDOW_WIDTH , .h = WINDOW_HEIGHT };
317+ try renderSession (renderer , & sessions [anim_state .focused_session ], full_rect , true );
318+ },
319+ .Expanding , .Collapsing = > {
320+ const animating_rect = anim_state .getCurrentRect (current_time );
321+
322+ for (sessions , 0.. ) | * session , i | {
323+ if (i != anim_state .focused_session ) {
324+ const grid_row : c_int = @intCast (i / GRID_COLS );
325+ const grid_col : c_int = @intCast (i % GRID_COLS );
326+
327+ const cell_rect = Rect {
328+ .x = grid_col * cell_width_pixels ,
329+ .y = grid_row * cell_height_pixels ,
330+ .w = cell_width_pixels ,
331+ .h = cell_height_pixels ,
332+ };
333+
334+ try renderSession (renderer , session , cell_rect , false );
335+ }
243336 }
244- }
245337
246- if (is_focused ) {
247- _ = c .SDL_SetRenderDrawColor (renderer , 100 , 150 , 255 , 255 );
248- } else {
249- _ = c .SDL_SetRenderDrawColor (renderer , 60 , 60 , 60 , 255 );
338+ try renderSession (renderer , & sessions [anim_state .focused_session ], animating_rect , true );
339+ },
340+ }
341+ }
342+
343+ fn renderSession (
344+ renderer : * c.SDL_Renderer ,
345+ session : * const SessionState ,
346+ rect : Rect ,
347+ is_focused : bool ,
348+ ) ! void {
349+ if (is_focused ) {
350+ _ = c .SDL_SetRenderDrawColor (renderer , 40 , 40 , 60 , 255 );
351+ } else {
352+ _ = c .SDL_SetRenderDrawColor (renderer , 20 , 20 , 20 , 255 );
353+ }
354+ const bg_rect = c.SDL_Rect {
355+ .x = rect .x ,
356+ .y = rect .y ,
357+ .w = rect .w ,
358+ .h = rect .h ,
359+ };
360+ _ = c .SDL_RenderFillRect (renderer , & bg_rect );
361+
362+ const screen = & session .terminal .screen ;
363+ const pages = screen .pages ;
364+
365+ var row : usize = 0 ;
366+ while (row < ROWS ) : (row += 1 ) {
367+ var col : usize = 0 ;
368+ while (col < COLS ) : (col += 1 ) {
369+ const list_cell = pages .getCell (.{ .active = .{
370+ .x = @intCast (col ),
371+ .y = @intCast (row ),
372+ } }) orelse continue ;
373+
374+ const cell = list_cell .cell ;
375+ const cp = cell .content .codepoint ;
376+ if (cp == 0 or cp == ' ' ) continue ;
377+
378+ const x : c_int = rect .x + @as (c_int , @intCast (col * CELL_WIDTH ));
379+ const y : c_int = rect .y + @as (c_int , @intCast (row * CELL_HEIGHT ));
380+
381+ _ = c .SDL_SetRenderDrawColor (renderer , 200 , 200 , 200 , 255 );
382+ const char_rect = c.SDL_Rect {
383+ .x = x ,
384+ .y = y ,
385+ .w = CELL_WIDTH ,
386+ .h = CELL_HEIGHT ,
387+ };
388+ _ = c .SDL_RenderFillRect (renderer , & char_rect );
250389 }
251- const border_rect = c.SDL_Rect {
252- .x = cell_x ,
253- .y = cell_y ,
254- .w = cell_width_pixels ,
255- .h = cell_height_pixels ,
256- };
257- _ = c .SDL_RenderDrawRect (renderer , & border_rect );
258390 }
391+
392+ if (is_focused ) {
393+ _ = c .SDL_SetRenderDrawColor (renderer , 100 , 150 , 255 , 255 );
394+ } else {
395+ _ = c .SDL_SetRenderDrawColor (renderer , 60 , 60 , 60 , 255 );
396+ }
397+ const border_rect = c.SDL_Rect {
398+ .x = rect .x ,
399+ .y = rect .y ,
400+ .w = rect .w ,
401+ .h = rect .h ,
402+ };
403+ _ = c .SDL_RenderDrawRect (renderer , & border_rect );
259404}
260405
261406fn encodeKey (key : c.SDL_Keysym , buf : []u8 ) ! usize {
0 commit comments