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