1 // This is an open source non-commercial project. Dear PVS-Studio, please check
2 // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
3
4 // Compositor: merge floating grids with the main grid for display in
5 // TUI and non-multigrid UIs.
6 //
7 // Layer-based compositing: https://en.wikipedia.org/wiki/Digital_compositing
8
9 #include <assert.h>
10 #include <limits.h>
11 #include <stdbool.h>
12 #include <stdio.h>
13
14 #include "nvim/api/private/helpers.h"
15 #include "nvim/ascii.h"
16 #include "nvim/highlight.h"
17 #include "nvim/lib/kvec.h"
18 #include "nvim/log.h"
19 #include "nvim/lua/executor.h"
20 #include "nvim/main.h"
21 #include "nvim/memory.h"
22 #include "nvim/message.h"
23 #include "nvim/os/os.h"
24 #include "nvim/popupmnu.h"
25 #include "nvim/screen.h"
26 #include "nvim/syntax.h"
27 #include "nvim/ugrid.h"
28 #include "nvim/ui.h"
29 #include "nvim/ui_compositor.h"
30 #include "nvim/vim.h"
31
32 #ifdef INCLUDE_GENERATED_DECLARATIONS
33 # include "ui_compositor.c.generated.h"
34 #endif
35
36 static UI *compositor = NULL;
37 static int composed_uis = 0;
38 kvec_t(ScreenGrid *) layers = KV_INITIAL_VALUE;
39
40 static size_t bufsize = 0;
41 static schar_T *linebuf;
42 static sattr_T *attrbuf;
43
44 #ifndef NDEBUG
45 static int chk_width = 0, chk_height = 0;
46 #endif
47
48 static ScreenGrid *curgrid;
49
50 static bool valid_screen = true;
51 static int msg_current_row = INT_MAX;
52 static bool msg_was_scrolled = false;
53
54 static int msg_sep_row = -1;
55 static schar_T msg_sep_char = { ' ', NUL };
56
57 static int dbghl_normal, dbghl_clear, dbghl_composed, dbghl_recompose;
58
ui_comp_init(void)59 void ui_comp_init(void)
60 {
61 if (compositor != NULL) {
62 return;
63 }
64 compositor = xcalloc(1, sizeof(UI));
65
66 compositor->rgb = true;
67 compositor->grid_resize = ui_comp_grid_resize;
68 compositor->grid_scroll = ui_comp_grid_scroll;
69 compositor->grid_cursor_goto = ui_comp_grid_cursor_goto;
70 compositor->raw_line = ui_comp_raw_line;
71 compositor->msg_set_pos = ui_comp_msg_set_pos;
72
73 // Be unopinionated: will be attached together with a "real" ui anyway
74 compositor->width = INT_MAX;
75 compositor->height = INT_MAX;
76 for (UIExtension i = 0; (int)i < kUIExtCount; i++) {
77 compositor->ui_ext[i] = true;
78 }
79
80 // TODO(bfredl): this will be more complicated if we implement
81 // hlstate per UI (i e reduce hl ids for non-hlstate UIs)
82 compositor->ui_ext[kUIHlState] = false;
83
84 kv_push(layers, &default_grid);
85 curgrid = &default_grid;
86
87 ui_attach_impl(compositor, 0);
88 }
89
ui_comp_syn_init(void)90 void ui_comp_syn_init(void)
91 {
92 dbghl_normal = syn_check_group(S_LEN("RedrawDebugNormal"));
93 dbghl_clear = syn_check_group(S_LEN("RedrawDebugClear"));
94 dbghl_composed = syn_check_group(S_LEN("RedrawDebugComposed"));
95 dbghl_recompose = syn_check_group(S_LEN("RedrawDebugRecompose"));
96 }
97
ui_comp_attach(UI * ui)98 void ui_comp_attach(UI *ui)
99 {
100 composed_uis++;
101 ui->composed = true;
102 }
103
ui_comp_detach(UI * ui)104 void ui_comp_detach(UI *ui)
105 {
106 composed_uis--;
107 if (composed_uis == 0) {
108 XFREE_CLEAR(linebuf);
109 XFREE_CLEAR(attrbuf);
110 bufsize = 0;
111 }
112 ui->composed = false;
113 }
114
ui_comp_should_draw(void)115 bool ui_comp_should_draw(void)
116 {
117 return composed_uis != 0 && valid_screen;
118 }
119
120 /// Places `grid` at (col,row) position with (width * height) size.
121 /// Adds `grid` as the top layer if it is a new layer.
122 ///
123 /// TODO(bfredl): later on the compositor should just use win_float_pos events,
124 /// though that will require slight event order adjustment: emit the win_pos
125 /// events in the beginning of update_screen(0), rather than in ui_flush()
ui_comp_put_grid(ScreenGrid * grid,int row,int col,int height,int width,bool valid,bool on_top)126 bool ui_comp_put_grid(ScreenGrid *grid, int row, int col, int height, int width, bool valid,
127 bool on_top)
128 {
129 bool moved;
130
131 grid->comp_height = height;
132 grid->comp_width = width;
133 if (grid->comp_index != 0) {
134 moved = (row != grid->comp_row) || (col != grid->comp_col);
135 if (ui_comp_should_draw()) {
136 // Redraw the area covered by the old position, and is not covered
137 // by the new position. Disable the grid so that compose_area() will not
138 // use it.
139 grid->comp_disabled = true;
140 compose_area(grid->comp_row, row,
141 grid->comp_col, grid->comp_col + grid->Columns);
142 if (grid->comp_col < col) {
143 compose_area(MAX(row, grid->comp_row),
144 MIN(row+height, grid->comp_row+grid->Rows),
145 grid->comp_col, col);
146 }
147 if (col+width < grid->comp_col+grid->Columns) {
148 compose_area(MAX(row, grid->comp_row),
149 MIN(row+height, grid->comp_row+grid->Rows),
150 col+width, grid->comp_col+grid->Columns);
151 }
152 compose_area(row+height, grid->comp_row+grid->Rows,
153 grid->comp_col, grid->comp_col + grid->Columns);
154 grid->comp_disabled = false;
155 }
156 grid->comp_row = row;
157 grid->comp_col = col;
158 } else {
159 moved = true;
160 #ifndef NDEBUG
161 for (size_t i = 0; i < kv_size(layers); i++) {
162 if (kv_A(layers, i) == grid) {
163 abort();
164 }
165 }
166 #endif
167
168 size_t insert_at = kv_size(layers);
169 while (insert_at > 0 && kv_A(layers, insert_at-1)->zindex > grid->zindex) {
170 insert_at--;
171 }
172
173 if (curwin && kv_A(layers, insert_at-1) == &curwin->w_grid_alloc
174 && kv_A(layers, insert_at-1)->zindex == grid->zindex
175 && !on_top) {
176 insert_at--;
177 }
178 // not found: new grid
179 kv_pushp(layers);
180 for (size_t i = kv_size(layers)-1; i > insert_at; i--) {
181 kv_A(layers, i) = kv_A(layers, i-1);
182 kv_A(layers, i)->comp_index = i;
183 }
184 kv_A(layers, insert_at) = grid;
185
186 grid->comp_row = row;
187 grid->comp_col = col;
188 grid->comp_index = insert_at;
189 }
190 if (moved && valid && ui_comp_should_draw()) {
191 compose_area(grid->comp_row, grid->comp_row+grid->Rows,
192 grid->comp_col, grid->comp_col+grid->Columns);
193 }
194 return moved;
195 }
196
ui_comp_remove_grid(ScreenGrid * grid)197 void ui_comp_remove_grid(ScreenGrid *grid)
198 {
199 assert(grid != &default_grid);
200 if (grid->comp_index == 0) {
201 // grid wasn't present
202 return;
203 }
204
205 if (curgrid == grid) {
206 curgrid = &default_grid;
207 }
208
209 for (size_t i = grid->comp_index; i < kv_size(layers)-1; i++) {
210 kv_A(layers, i) = kv_A(layers, i+1);
211 kv_A(layers, i)->comp_index = i;
212 }
213 (void)kv_pop(layers);
214 grid->comp_index = 0;
215
216 // recompose the area under the grid
217 // inefficient when being overlapped: only draw up to grid->comp_index
218 ui_comp_compose_grid(grid);
219 }
220
ui_comp_set_grid(handle_T handle)221 bool ui_comp_set_grid(handle_T handle)
222 {
223 if (curgrid->handle == handle) {
224 return true;
225 }
226 ScreenGrid *grid = NULL;
227 for (size_t i = 0; i < kv_size(layers); i++) {
228 if (kv_A(layers, i)->handle == handle) {
229 grid = kv_A(layers, i);
230 break;
231 }
232 }
233 if (grid != NULL) {
234 curgrid = grid;
235 return true;
236 }
237 return false;
238 }
239
ui_comp_raise_grid(ScreenGrid * grid,size_t new_index)240 static void ui_comp_raise_grid(ScreenGrid *grid, size_t new_index)
241 {
242 size_t old_index = grid->comp_index;
243 for (size_t i = old_index; i < new_index; i++) {
244 kv_A(layers, i) = kv_A(layers, i+1);
245 kv_A(layers, i)->comp_index = i;
246 }
247 kv_A(layers, new_index) = grid;
248 grid->comp_index = new_index;
249 for (size_t i = old_index; i < new_index; i++) {
250 ScreenGrid *grid2 = kv_A(layers, i);
251 int startcol = MAX(grid->comp_col, grid2->comp_col);
252 int endcol = MIN(grid->comp_col+grid->Columns,
253 grid2->comp_col+grid2->Columns);
254 compose_area(MAX(grid->comp_row, grid2->comp_row),
255 MIN(grid->comp_row+grid->Rows, grid2->comp_row+grid2->Rows),
256 startcol, endcol);
257 }
258 }
259
ui_comp_grid_cursor_goto(UI * ui,Integer grid_handle,Integer r,Integer c)260 static void ui_comp_grid_cursor_goto(UI *ui, Integer grid_handle, Integer r, Integer c)
261 {
262 if (!ui_comp_should_draw() || !ui_comp_set_grid((int)grid_handle)) {
263 return;
264 }
265 int cursor_row = curgrid->comp_row+(int)r;
266 int cursor_col = curgrid->comp_col+(int)c;
267
268 // TODO(bfredl): maybe not the best time to do this, for efficiency we
269 // should configure all grids before entering win_update()
270 if (curgrid != &default_grid) {
271 size_t new_index = kv_size(layers)-1;
272
273 while (new_index > 1 && kv_A(layers, new_index)->zindex > curgrid->zindex) {
274 new_index--;
275 }
276
277 if (curgrid->comp_index < new_index) {
278 ui_comp_raise_grid(curgrid, new_index);
279 }
280 }
281
282 if (cursor_col >= default_grid.Columns || cursor_row >= default_grid.Rows) {
283 // TODO(bfredl): this happens with 'writedelay', refactor?
284 // abort();
285 return;
286 }
287 ui_composed_call_grid_cursor_goto(1, cursor_row, cursor_col);
288 }
289
ui_comp_mouse_focus(int row,int col)290 ScreenGrid *ui_comp_mouse_focus(int row, int col)
291 {
292 for (ssize_t i = (ssize_t)kv_size(layers)-1; i > 0; i--) {
293 ScreenGrid *grid = kv_A(layers, i);
294 if (grid->focusable
295 && row >= grid->comp_row && row < grid->comp_row+grid->Rows
296 && col >= grid->comp_col && col < grid->comp_col+grid->Columns) {
297 return grid;
298 }
299 }
300 return NULL;
301 }
302
303 /// Baseline implementation. This is always correct, but we can sometimes
304 /// do something more efficient (where efficiency means smaller deltas to
305 /// the downstream UI.)
compose_line(Integer row,Integer startcol,Integer endcol,LineFlags flags)306 static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlags flags)
307 {
308 // If rightleft is set, startcol may be -1. In such cases, the assertions
309 // will fail because no overlap is found. Adjust startcol to prevent it.
310 startcol = MAX(startcol, 0);
311 // in case we start on the right half of a double-width char, we need to
312 // check the left half. But skip it in output if it wasn't doublewidth.
313 int skipstart = 0, skipend = 0;
314 if (startcol > 0 && (flags & kLineFlagInvalid)) {
315 startcol--;
316 skipstart = 1;
317 }
318 if (endcol < default_grid.Columns && (flags & kLineFlagInvalid)) {
319 endcol++;
320 skipend = 1;
321 }
322
323 int col = (int)startcol;
324 ScreenGrid *grid = NULL;
325 schar_T *bg_line = &default_grid.chars[default_grid.line_offset[row]
326 +(size_t)startcol];
327 sattr_T *bg_attrs = &default_grid.attrs[default_grid.line_offset[row]
328 +(size_t)startcol];
329
330 int grid_width, grid_height;
331 while (col < endcol) {
332 int until = 0;
333 for (size_t i = 0; i < kv_size(layers); i++) {
334 ScreenGrid *g = kv_A(layers, i);
335 // compose_line may have been called after a shrinking operation but
336 // before the resize has actually been applied. Therefore, we need to
337 // first check to see if any grids have pending updates to width/height,
338 // to ensure that we don't accidentally put any characters into `linebuf`
339 // that have been invalidated.
340 grid_width = MIN(g->Columns, g->comp_width);
341 grid_height = MIN(g->Rows, g->comp_height);
342 if (g->comp_row > row || row >= g->comp_row + grid_height
343 || g->comp_disabled) {
344 continue;
345 }
346 if (g->comp_col <= col && col < g->comp_col + grid_width) {
347 grid = g;
348 until = g->comp_col + grid_width;
349 } else if (g->comp_col > col) {
350 until = MIN(until, g->comp_col);
351 }
352 }
353 until = MIN(until, (int)endcol);
354
355 assert(grid != NULL);
356 assert(until > col);
357 assert(until <= default_grid.Columns);
358 size_t n = (size_t)(until-col);
359
360 if (row == msg_sep_row && grid->comp_index <= msg_grid.comp_index) {
361 // TODO(bfredl): when we implement borders around floating windows, then
362 // msgsep can just be a border "around" the message grid.
363 grid = &msg_grid;
364 sattr_T msg_sep_attr = (sattr_T)HL_ATTR(HLF_MSGSEP);
365 for (int i = col; i < until; i++) {
366 memcpy(linebuf[i-startcol], msg_sep_char, sizeof(*linebuf));
367 attrbuf[i-startcol] = msg_sep_attr;
368 }
369 } else {
370 size_t off = grid->line_offset[row-grid->comp_row]
371 + (size_t)(col-grid->comp_col);
372 memcpy(linebuf+(col-startcol), grid->chars+off, n * sizeof(*linebuf));
373 memcpy(attrbuf+(col-startcol), grid->attrs+off, n * sizeof(*attrbuf));
374 if (grid->comp_col+grid->Columns > until
375 && grid->chars[off+n][0] == NUL) {
376 linebuf[until-1-startcol][0] = ' ';
377 linebuf[until-1-startcol][1] = '\0';
378 if (col == startcol && n == 1) {
379 skipstart = 0;
380 }
381 }
382 }
383
384 // 'pumblend' and 'winblend'
385 if (grid->blending) {
386 int width;
387 for (int i = col-(int)startcol; i < until-startcol; i += width) {
388 width = 1;
389 // negative space
390 bool thru = strequal((char *)linebuf[i], " ") && bg_line[i][0] != NUL;
391 if (i+1 < endcol-startcol && bg_line[i+1][0] == NUL) {
392 width = 2;
393 thru &= strequal((char *)linebuf[i+1], " ");
394 }
395 attrbuf[i] = (sattr_T)hl_blend_attrs(bg_attrs[i], attrbuf[i], &thru);
396 if (width == 2) {
397 attrbuf[i+1] = (sattr_T)hl_blend_attrs(bg_attrs[i+1],
398 attrbuf[i+1], &thru);
399 }
400 if (thru) {
401 memcpy(linebuf + i, bg_line + i, (size_t)width * sizeof(linebuf[i]));
402 }
403 }
404 }
405
406 // Tricky: if overlap caused a doublewidth char to get cut-off, must
407 // replace the visible half with a space.
408 if (linebuf[col-startcol][0] == NUL) {
409 linebuf[col-startcol][0] = ' ';
410 linebuf[col-startcol][1] = NUL;
411 if (col == endcol-1) {
412 skipend = 0;
413 }
414 } else if (n > 1 && linebuf[col-startcol+1][0] == NUL) {
415 skipstart = 0;
416 }
417
418 col = until;
419 }
420 if (linebuf[endcol-startcol-1][0] == NUL) {
421 skipend = 0;
422 }
423
424 assert(endcol <= chk_width);
425 assert(row < chk_height);
426
427 if (!(grid && grid == &default_grid)) {
428 // TODO(bfredl): too conservative, need check
429 // grid->line_wraps if grid->Width == Width
430 flags = flags & ~kLineFlagWrap;
431 }
432
433 for (int i = skipstart; i < (endcol-skipend)-startcol; i++) {
434 if (attrbuf[i] < 0) {
435 if (rdb_flags & RDB_INVALID) {
436 abort();
437 } else {
438 attrbuf[i] = 0;
439 }
440 }
441 }
442 ui_composed_call_raw_line(1, row, startcol+skipstart,
443 endcol-skipend, endcol-skipend, 0, flags,
444 (const schar_T *)linebuf+skipstart,
445 (const sattr_T *)attrbuf+skipstart);
446 }
447
compose_debug(Integer startrow,Integer endrow,Integer startcol,Integer endcol,int syn_id,bool delay)448 static void compose_debug(Integer startrow, Integer endrow, Integer startcol, Integer endcol,
449 int syn_id, bool delay)
450 {
451 if (!(rdb_flags & RDB_COMPOSITOR)) {
452 return;
453 }
454
455 endrow = MIN(endrow, default_grid.Rows);
456 endcol = MIN(endcol, default_grid.Columns);
457 int attr = syn_id2attr(syn_id);
458
459 for (int row = (int)startrow; row < endrow; row++) {
460 ui_composed_call_raw_line(1, row, startcol, startcol, endcol, attr, false,
461 (const schar_T *)linebuf,
462 (const sattr_T *)attrbuf);
463 }
464
465
466 if (delay) {
467 debug_delay(endrow-startrow);
468 }
469 }
470
debug_delay(Integer lines)471 static void debug_delay(Integer lines)
472 {
473 ui_call_flush();
474 uint64_t wd = (uint64_t)labs(p_wd);
475 uint64_t factor = (uint64_t)MAX(MIN(lines, 5), 1);
476 os_microdelay(factor * wd * 1000u, true);
477 }
478
479
compose_area(Integer startrow,Integer endrow,Integer startcol,Integer endcol)480 static void compose_area(Integer startrow, Integer endrow, Integer startcol, Integer endcol)
481 {
482 compose_debug(startrow, endrow, startcol, endcol, dbghl_recompose, true);
483 endrow = MIN(endrow, default_grid.Rows);
484 endcol = MIN(endcol, default_grid.Columns);
485 if (endcol <= startcol) {
486 return;
487 }
488 for (int r = (int)startrow; r < endrow; r++) {
489 compose_line(r, startcol, endcol, kLineFlagInvalid);
490 }
491 }
492
493 /// compose the area under the grid.
494 ///
495 /// This is needed when some option affecting composition is changed,
496 /// such as 'pumblend' for popupmenu grid.
ui_comp_compose_grid(ScreenGrid * grid)497 void ui_comp_compose_grid(ScreenGrid *grid)
498 {
499 if (ui_comp_should_draw()) {
500 compose_area(grid->comp_row, grid->comp_row+grid->Rows,
501 grid->comp_col, grid->comp_col+grid->Columns);
502 }
503 }
504
ui_comp_raw_line(UI * ui,Integer grid,Integer row,Integer startcol,Integer endcol,Integer clearcol,Integer clearattr,LineFlags flags,const schar_T * chunk,const sattr_T * attrs)505 static void ui_comp_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Integer endcol,
506 Integer clearcol, Integer clearattr, LineFlags flags,
507 const schar_T *chunk, const sattr_T *attrs)
508 {
509 if (!ui_comp_should_draw() || !ui_comp_set_grid((int)grid)) {
510 return;
511 }
512
513 row += curgrid->comp_row;
514 startcol += curgrid->comp_col;
515 endcol += curgrid->comp_col;
516 clearcol += curgrid->comp_col;
517 if (curgrid != &default_grid) {
518 flags = flags & ~kLineFlagWrap;
519 }
520
521 assert(endcol <= clearcol);
522
523 // TODO(bfredl): this should not really be necessary. But on some condition
524 // when resizing nvim, a window will be attempted to be drawn on the older
525 // and possibly larger global screen size.
526 if (row >= default_grid.Rows) {
527 DLOG("compositor: invalid row %" PRId64 " on grid %" PRId64, row, grid);
528 return;
529 }
530 if (clearcol > default_grid.Columns) {
531 DLOG("compositor: invalid last column %" PRId64 " on grid %" PRId64,
532 clearcol, grid);
533 if (startcol >= default_grid.Columns) {
534 return;
535 }
536 clearcol = default_grid.Columns;
537 endcol = MIN(endcol, clearcol);
538 }
539
540 bool covered = curgrid_covered_above((int)row);
541 // TODO(bfredl): eventually should just fix compose_line to respect clearing
542 // and optimize it for uncovered lines.
543 if (flags & kLineFlagInvalid || covered || curgrid->blending) {
544 compose_debug(row, row+1, startcol, clearcol, dbghl_composed, true);
545 compose_line(row, startcol, clearcol, flags);
546 } else {
547 compose_debug(row, row+1, startcol, endcol, dbghl_normal, false);
548 compose_debug(row, row+1, endcol, clearcol, dbghl_clear, true);
549 #ifndef NDEBUG
550 for (int i = 0; i < endcol-startcol; i++) {
551 assert(attrs[i] >= 0);
552 }
553 #endif
554 ui_composed_call_raw_line(1, row, startcol, endcol, clearcol, clearattr,
555 flags, chunk, attrs);
556 }
557 }
558
559 /// The screen is invalid and will soon be cleared
560 ///
561 /// Don't redraw floats until screen is cleared
ui_comp_set_screen_valid(bool valid)562 void ui_comp_set_screen_valid(bool valid)
563 {
564 valid_screen = valid;
565 if (!valid) {
566 msg_sep_row = -1;
567 }
568 }
569
ui_comp_msg_set_pos(UI * ui,Integer grid,Integer row,Boolean scrolled,String sep_char)570 static void ui_comp_msg_set_pos(UI *ui, Integer grid, Integer row, Boolean scrolled,
571 String sep_char)
572 {
573 msg_grid.comp_row = (int)row;
574 if (scrolled && row > 0) {
575 msg_sep_row = (int)row-1;
576 if (sep_char.data) {
577 STRLCPY(msg_sep_char, sep_char.data, sizeof(msg_sep_char));
578 }
579 } else {
580 msg_sep_row = -1;
581 }
582
583 if (row > msg_current_row && ui_comp_should_draw()) {
584 compose_area(MAX(msg_current_row-1, 0), row, 0, default_grid.Columns);
585 } else if (row < msg_current_row && ui_comp_should_draw()
586 && msg_current_row < Rows) {
587 int delta = msg_current_row - (int)row;
588 if (msg_grid.blending) {
589 int first_row = MAX((int)row-(scrolled?1:0), 0);
590 compose_area(first_row, Rows-delta, 0, Columns);
591 } else {
592 // scroll separator together with message text
593 int first_row = MAX((int)row-(msg_was_scrolled?1:0), 0);
594 ui_composed_call_grid_scroll(1, first_row, Rows, 0, Columns, delta, 0);
595 if (scrolled && !msg_was_scrolled && row > 0) {
596 compose_area(row-1, row, 0, Columns);
597 }
598 }
599 }
600
601 msg_current_row = (int)row;
602 msg_was_scrolled = scrolled;
603 }
604
605 /// check if curgrid is covered on row or above
606 ///
607 /// TODO(bfredl): currently this only handles message row
curgrid_covered_above(int row)608 static bool curgrid_covered_above(int row)
609 {
610 bool above_msg = (kv_A(layers, kv_size(layers)-1) == &msg_grid
611 && row < msg_current_row-(msg_was_scrolled?1:0));
612 return kv_size(layers)-(above_msg?1:0) > curgrid->comp_index+1;
613 }
614
ui_comp_grid_scroll(UI * ui,Integer grid,Integer top,Integer bot,Integer left,Integer right,Integer rows,Integer cols)615 static void ui_comp_grid_scroll(UI *ui, Integer grid, Integer top, Integer bot, Integer left,
616 Integer right, Integer rows, Integer cols)
617 {
618 if (!ui_comp_should_draw() || !ui_comp_set_grid((int)grid)) {
619 return;
620 }
621 top += curgrid->comp_row;
622 bot += curgrid->comp_row;
623 left += curgrid->comp_col;
624 right += curgrid->comp_col;
625 bool covered = curgrid_covered_above((int)(bot - MAX(rows, 0)));
626
627 if (covered || curgrid->blending) {
628 // TODO(bfredl):
629 // 1. check if rectangles actually overlap
630 // 2. calculate subareas that can scroll.
631 compose_debug(top, bot, left, right, dbghl_recompose, true);
632 for (int r = (int)(top + MAX(-rows, 0)); r < bot - MAX(rows, 0); r++) {
633 // TODO(bfredl): workaround for win_update() performing two scrolls in a
634 // row, where the latter might scroll invalid space created by the first.
635 // ideally win_update() should keep track of this itself and not scroll
636 // the invalid space.
637 if (curgrid->attrs[curgrid->line_offset[r-curgrid->comp_row]
638 +left-curgrid->comp_col] >= 0) {
639 compose_line(r, left, right, 0);
640 }
641 }
642 } else {
643 ui_composed_call_grid_scroll(1, top, bot, left, right, rows, cols);
644 if (rdb_flags & RDB_COMPOSITOR) {
645 debug_delay(2);
646 }
647 }
648 }
649
ui_comp_grid_resize(UI * ui,Integer grid,Integer width,Integer height)650 static void ui_comp_grid_resize(UI *ui, Integer grid, Integer width, Integer height)
651 {
652 if (grid == 1) {
653 ui_composed_call_grid_resize(1, width, height);
654 #ifndef NDEBUG
655 chk_width = (int)width;
656 chk_height = (int)height;
657 #endif
658 size_t new_bufsize = (size_t)width;
659 if (bufsize != new_bufsize) {
660 xfree(linebuf);
661 xfree(attrbuf);
662 linebuf = xmalloc(new_bufsize * sizeof(*linebuf));
663 attrbuf = xmalloc(new_bufsize * sizeof(*attrbuf));
664 bufsize = new_bufsize;
665 }
666 }
667 }
668
669