From 9a498038d681e65748b5223d814758a6b7a47693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 24 Nov 2020 19:00:57 +0100 Subject: [PATCH] =?UTF-8?q?resize:=20don=E2=80=99t=20reflow=20text=20on=20?= =?UTF-8?q?alt=20screen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Alt screen applications normally reflow/readjust themselves on a window resize. When we do it too, the result is graphical glitches/flashes since our re-flowed text is rendered in one frame, and the application re-flowed text soon thereafter. We can’t avoid rendering some kind of re-flowed frame, since we don’t know when, or even if, the application will update itself. To avoid glitches, we need to render, as closely as possible, what the application itself will render shortly. This is actually pretty simple; we just need to copy the visible content over from the old grid to the new grid. We don’t bother with text re-flow, but simply truncate long lines. To simplify things, we simply cancel any active selection (since often times, it will be corrupted anyway when the application redraws itself). Since we’re not reflowing text, there’s no need to translate e.g. the cursor position - we just keep the current position (but bounded to the new dimensions). Fun thing: ‘less’ gets corrupted if we don’t leave the cursor at the (new) bottom row. To handle this, we check if the cursor (before resize) is at the bottom row, and if so, we move it to the new bottom row. Closes #221 --- CHANGELOG.md | 3 ++ grid.c | 131 +++++++++++++++++++++++++++++++++++++++++++++++---- grid.h | 9 +++- render.c | 17 ++++--- 4 files changed, 140 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd57185d..3e4fb641 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -92,6 +92,9 @@ means foot can be PGO:d in e.g. sandboxed build scripts. See this. * Sixel default maximum size is now 10000x10000 instead of the current window size. +* Graphical glitches/flashes when resizing the window while running a + fullscreen application, i.e. the 'alt' screen + (https://codeberg.org/dnkl/foot/issues/221). ### Deprecated diff --git a/grid.c b/grid.c index a779c1b3..b6cb4223 100644 --- a/grid.c +++ b/grid.c @@ -55,12 +55,125 @@ grid_row_free(struct row *row) } void -grid_reflow(struct grid *grid, int new_rows, int new_cols, - int old_screen_rows, int new_screen_rows, - size_t tracking_points_count, - struct coord *const _tracking_points[static tracking_points_count], - size_t compose_count, const struct - composed composed[static compose_count]) +grid_resize_without_reflow( + struct grid *grid, int new_rows, int new_cols, + int old_screen_rows, int new_screen_rows) +{ + struct row *const *old_grid = grid->rows; + const int old_rows = grid->num_rows; + const int old_cols = grid->num_cols; + + struct row **new_grid = xcalloc(new_rows, sizeof(new_grid[0])); + + tll(struct sixel) untranslated_sixels = tll_init(); + tll_foreach(grid->sixel_images, it) + tll_push_back(untranslated_sixels, it->item); + tll_free(grid->sixel_images); + + int new_offset = 0; + + /* Copy old lines, truncating them if old rows were longer */ + for (int r = 0; r < min(old_screen_rows, new_screen_rows); r++) { + const int old_row_idx = (grid->offset + r) & (old_rows - 1); + const int new_row_idx = (new_offset + r) & (new_rows - 1); + + const struct row *old_row = old_grid[old_row_idx]; + assert(old_row != NULL); + + struct row *new_row = grid_row_alloc(new_cols, false); + new_grid[new_row_idx] = new_row; + + memcpy(new_row->cells, + old_row->cells, + sizeof(struct cell) * min(old_cols, new_cols)); + + new_row->dirty = old_row->dirty; + new_row->linebreak = false; + + /* Clear "new" columns */ + if (new_cols > old_cols) { + memset(&new_row->cells[old_cols], 0, + sizeof(struct cell) * (new_cols - old_cols)); + new_row->dirty = true; + } + + /* Map sixels on current "old" row to current "new row" */ + tll_foreach(untranslated_sixels, it) { + if (it->item.pos.row != old_row_idx) + continue; + + struct sixel sixel = it->item; + sixel.pos.row = new_row_idx; + + if (sixel.pos.col < new_cols) + tll_push_back(grid->sixel_images, sixel); + else + sixel_destroy(&it->item); + tll_remove(untranslated_sixels, it); + } + } + + /* Clear "new" lines */ + for (int r = min(old_screen_rows, new_screen_rows); r < new_screen_rows; r++) { + struct row *new_row = grid_row_alloc(new_cols, false); + new_grid[(new_offset + r) & (new_rows - 1)] = new_row; + + memset(new_row->cells, 0, sizeof(struct cell) * new_cols); + new_row->linebreak = false; + new_row->dirty = true; + } + + /* Free old grid */ + for (int r = 0; r < grid->num_rows; r++) + grid_row_free(old_grid[r]); + free(grid->rows); + + grid->rows = new_grid; + grid->num_rows = new_rows; + grid->num_cols = new_cols; + + grid->view = grid->offset = new_offset; + + /* Keep cursor at current position, but clamp to new dimensions */ + struct coord cursor = grid->cursor.point; + if (cursor.row == old_screen_rows - 1) { + /* 'less' breaks if the cursor isn't at the bottom */ + cursor.row = new_screen_rows - 1; + } + cursor.row = min(cursor.row, new_screen_rows - 1); + cursor.col = min(cursor.col, new_cols - 1); + grid->cursor.point = cursor; + + struct coord saved_cursor = grid->saved_cursor.point; + if (saved_cursor.row == old_screen_rows - 1) + saved_cursor.row = new_screen_rows - 1; + saved_cursor.row = min(saved_cursor.row, new_screen_rows - 1); + saved_cursor.col = min(saved_cursor.col, new_cols - 1); + grid->saved_cursor.point = saved_cursor; + + grid->cur_row = new_grid[(grid->offset + cursor.row) & (new_rows - 1)]; + grid->cursor.lcf = false; + grid->saved_cursor.lcf = false; + + /* Free sixels we failed to "map" to the new grid */ + tll_foreach(untranslated_sixels, it) + sixel_destroy(&it->item); + tll_free(untranslated_sixels); + +#if defined(_DEBUG) + for (int r = 0; r < new_screen_rows; r++) + grid_row_in_view(grid, r); +#endif +} + +void +grid_resize_and_reflow( + struct grid *grid, int new_rows, int new_cols, + int old_screen_rows, int new_screen_rows, + size_t tracking_points_count, + struct coord *const _tracking_points[static tracking_points_count], + size_t compose_count, const struct + composed composed[static compose_count]) { struct row *const *old_grid = grid->rows; const int old_rows = grid->num_rows; @@ -305,7 +418,6 @@ grid_reflow(struct grid *grid, int new_rows, int new_cols, grid_row_free(old_grid[r]); free(grid->rows); - grid->cur_row = new_grid[cursor.row]; grid->rows = new_grid; grid->num_rows = new_rows; grid->num_cols = new_cols; @@ -315,14 +427,15 @@ grid_reflow(struct grid *grid, int new_rows, int new_cols, while (cursor.row < 0) cursor.row += grid->num_rows; cursor.row = min(cursor.row, new_screen_rows - 1); - assert(cursor.col >= 0 && cursor.col < new_cols); + cursor.col = min(cursor.col, new_cols - 1); saved_cursor.row -= grid->offset; while (saved_cursor.row < 0) saved_cursor.row += grid->num_rows; saved_cursor.row = min(saved_cursor.row, new_screen_rows - 1); - assert(saved_cursor.col >= 0 && saved_cursor.col < new_cols); + saved_cursor.col = min(saved_cursor.col, new_cols - 1); + grid->cur_row = new_grid[(grid->offset + cursor.row) & (new_rows - 1)]; grid->cursor.point = cursor; grid->saved_cursor.point = saved_cursor; diff --git a/grid.h b/grid.h index cbe60536..1066e28f 100644 --- a/grid.h +++ b/grid.h @@ -6,11 +6,16 @@ void grid_swap_row(struct grid *grid, int row_a, int row_b); struct row *grid_row_alloc(int cols, bool initialize); void grid_row_free(struct row *row); -void grid_reflow( + +void grid_resize_without_reflow( + struct grid *grid, int new_rows, int new_cols, + int old_screen_rows, int new_screen_rows); + +void grid_resize_and_reflow( struct grid *grid, int new_rows, int new_cols, int old_screen_rows, int new_screen_rows, size_t tracking_points_count, - struct coord *const tracking_points[static tracking_points_count], + struct coord *const _tracking_points[static tracking_points_count], size_t compose_count, const struct composed composed[static compose_count]); diff --git a/render.c b/render.c index b4afab83..53363071 100644 --- a/render.c +++ b/render.c @@ -2276,23 +2276,22 @@ maybe_resize(struct terminal *term, int width, int height, bool force) goto damage_view; } + if (term->grid == &term->alt) + selection_cancel(term); + struct coord *const tracking_points[] = { &term->selection.start, &term->selection.end, }; - /* Reflow grids */ - grid_reflow( + /* Resize grids */ + grid_resize_and_reflow( &term->normal, new_normal_grid_rows, new_cols, old_rows, new_rows, - term->grid == &term->normal ? ALEN(tracking_points) : 0, - term->grid == &term->normal ? tracking_points : NULL, + ALEN(tracking_points), tracking_points, term->composed_count, term->composed); - grid_reflow( - &term->alt, new_alt_grid_rows, new_cols, old_rows, new_rows, - term->grid == &term->alt ? ALEN(tracking_points) : 0, - term->grid == &term->alt ? tracking_points : NULL, - term->composed_count, term->composed); + grid_resize_without_reflow( + &term->alt, new_alt_grid_rows, new_cols, old_rows, new_rows); /* Reset tab stops */ tll_free(term->tab_stops);