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