patch 9.2.0189: MS-Windows: opacity popups flicker during redraw in the console
Commit: https://github.com/vim/vim/commit/019c53b37f1b2cf16825cc6f1fa38c25a6f7a9bd Author: Yasuhiro Matsumoto <[email protected]> Date: Tue Mar 17 20:51:22 2026 +0000 patch 9.2.0189: MS-Windows: opacity popups flicker during redraw in the console Problem: When using transparent popups in the Win32 console, redrawing background windows causes flickering. This happens because the background is drawn opaquely before the popup blends and draws on top. Solution: Implement a Z-index mask to suppress screen_char() output for cells covered by an opacity popup. Disable the Clear-to-EOL (T_CE) optimization for lines overlapping these popups to prevent accidental erasure (Yasuhiro Matsumoto). closes: #19697 Signed-off-by: Yasuhiro Matsumoto <[email protected]> Signed-off-by: Christian Brabandt <[email protected]> diff --git a/src/popupwin.c b/src/popupwin.c index 23588c7ca..fe06777f7 100644 --- a/src/popupwin.c +++ b/src/popupwin.c @@ -4256,6 +4256,41 @@ popup_need_position_adjust(win_T *wp) return wp->w_cursor.lnum != wp->w_popup_last_curline; } +// Cached array with max zindex of opacity popups covering each cell. +// Allocated in may_update_popup_mask() when opacity popups exist. +static short *opacity_zindex = NULL; +static int opacity_zindex_rows = 0; +static int opacity_zindex_cols = 0; + +/* + * Mark cells covered by opacity popup "wp" in opacity_zindex[]. + * Stores the maximum zindex so that lower popups can be suppressed too. + */ + static void +popup_mark_opacity_zindex(win_T *wp) +{ + int width; + int height; + int r, c; + + if (!(wp->w_popup_flags & POPF_OPACITY) || wp->w_popup_blend <= 0 + || (wp->w_popup_flags & POPF_HIDDEN)) + return; + + width = popup_width(wp); + height = popup_height(wp); + for (r = wp->w_winrow; + r < wp->w_winrow + height && r < screen_Rows; ++r) + for (c = wp->w_wincol; + c < wp->w_wincol + width - wp->w_popup_leftoff + && c < screen_Columns; ++c) + { + int off = r * screen_Columns + c; + if (wp->w_zindex > opacity_zindex[off]) + opacity_zindex[off] = wp->w_zindex; + } +} + /* * Force background windows to redraw rows under an opacity popup. */ @@ -4278,16 +4313,55 @@ redraw_win_under_opacity_popup(win_T *wp) win_T *twp; twp = mouse_find_win(&line_cp, &col_cp, IGNORE_POPUP); - if (twp != NULL && line_cp < twp->w_height) + if (twp != NULL) { - linenr_T lnum; + if (line_cp < twp->w_height) + { + linenr_T lnum; - (void)mouse_comp_pos(twp, &line_cp, &col_cp, &lnum, NULL); - redrawWinline(twp, lnum); + (void)mouse_comp_pos(twp, &line_cp, &col_cp, &lnum, NULL); + redrawWinline(twp, lnum); + } + else if (line_cp == twp->w_height) + // Status bar line: mark for redraw to prevent + // opacity blend accumulation. + twp->w_redr_status = TRUE; } } } + +/* + * Return TRUE if cell (row, col) is covered by a higher-zindex opacity popup. + */ + int +popup_is_under_opacity(int row, int col) +{ + if (opacity_zindex == NULL + || row < 0 || row >= opacity_zindex_rows + || col < 0 || col >= opacity_zindex_cols) + return FALSE; + return opacity_zindex[row * opacity_zindex_cols + col] > screen_zindex; +} + +/* + * Return TRUE if any cell in row "row" from "start_col" to "end_col" + * (exclusive) is covered by a higher-zindex opacity popup. + */ + int +popup_is_under_opacity_range(int row, int start_col, int end_col) +{ + int col; + + if (opacity_zindex == NULL + || row < 0 || row >= opacity_zindex_rows) + return FALSE; + for (col = start_col; col < end_col && col < opacity_zindex_cols; ++col) + if (opacity_zindex[row * opacity_zindex_cols + col] > screen_zindex) + return TRUE; + return FALSE; +} + /* * Update "popup_mask" if needed. * Also recomputes the popup size and positions. @@ -4330,10 +4404,58 @@ may_update_popup_mask(int type) // wouldn't normally be redrawn. Without this, ScreenAttrs retains // blended values from the previous cycle, causing blend accumulation. // This must run every cycle, not just when popup_mask_refresh is set. - FOR_ALL_POPUPWINS(wp) - redraw_win_under_opacity_popup(wp); - FOR_ALL_POPUPWINS_IN_TAB(curtab, wp) - redraw_win_under_opacity_popup(wp); + // + // Also build the opacity_zindex array used by screen_char() to suppress + // output for cells under opacity popups during background draw. + { + int has_opacity = FALSE; + + FOR_ALL_POPUPWINS(wp) + { + redraw_win_under_opacity_popup(wp); + if ((wp->w_popup_flags & POPF_OPACITY) + && wp->w_popup_blend > 0 + && !(wp->w_popup_flags & POPF_HIDDEN)) + has_opacity = TRUE; + } + FOR_ALL_POPUPWINS_IN_TAB(curtab, wp) + { + redraw_win_under_opacity_popup(wp); + if ((wp->w_popup_flags & POPF_OPACITY) + && wp->w_popup_blend > 0 + && !(wp->w_popup_flags & POPF_HIDDEN)) + has_opacity = TRUE; + } + + if (!has_opacity) + { + VIM_CLEAR(opacity_zindex); + opacity_zindex_rows = 0; + opacity_zindex_cols = 0; + } + else + { + if (opacity_zindex_rows != screen_Rows + || opacity_zindex_cols != screen_Columns) + { + vim_free(opacity_zindex); + opacity_zindex = LALLOC_MULT(short, + screen_Rows * screen_Columns); + opacity_zindex_rows = screen_Rows; + opacity_zindex_cols = screen_Columns; + } + if (opacity_zindex != NULL) + { + vim_memset(opacity_zindex, 0, + (size_t)screen_Rows * screen_Columns * sizeof(short)); + + FOR_ALL_POPUPWINS(wp) + popup_mark_opacity_zindex(wp); + FOR_ALL_POPUPWINS_IN_TAB(curtab, wp) + popup_mark_opacity_zindex(wp); + } + } + } if (!popup_mask_refresh) return; diff --git a/src/proto/popupwin.pro b/src/proto/popupwin.pro index ee8efbb14..d6ac2b72a 100644 --- a/src/proto/popupwin.pro +++ b/src/proto/popupwin.pro @@ -52,6 +52,8 @@ win_T *find_next_popup(int lowest, int handled_flag); int popup_do_filter(int c); int popup_no_mapping(void); void popup_check_cursor_pos(void); +int popup_is_under_opacity(int row, int col); +int popup_is_under_opacity_range(int row, int start_col, int end_col); void may_update_popup_mask(int type); void may_update_popup_position(void); int popup_get_base_screen_cell(int row, int col, schar_T *linep, int *attrp, u8char_T *ucp); diff --git a/src/screen.c b/src/screen.c index acef4d38b..48261009f 100644 --- a/src/screen.c +++ b/src/screen.c @@ -600,7 +600,13 @@ screen_line( { ScreenLines[off_to - 1] = ' '; ScreenLinesUC[off_to - 1] = 0; - screen_char(off_to - 1, row, col + coloff - 1); + // Skip screen output when drawing an opacity popup: the + // background draw already output this cell, and outputting + // a space here would briefly erase it causing flicker. +# ifdef FEAT_PROP_POPUP + if (screen_opacity_popup == NULL) +# endif + screen_char(off_to - 1, row, col + coloff - 1); } #endif @@ -1004,7 +1010,13 @@ skip_opacity: ScreenLines[off_to] = ' '; if (enc_utf8) ScreenLinesUC[off_to] = 0; - screen_char(off_to, row, col + coloff); + // Skip screen output when drawing an opacity popup: the + // background already has this cell, outputting a space here + // would briefly erase it causing flicker. +#ifdef FEAT_PROP_POPUP + if (screen_opacity_popup == NULL) +#endif + screen_char(off_to, row, col + coloff); } if (clear_width > 0 @@ -2224,6 +2236,23 @@ screen_char(unsigned off, int row, int col) if (row >= screen_Rows || col >= screen_Columns) return; +#ifdef FEAT_PROP_POPUP + // If this cell is under a higher-zindex opacity popup, suppress + // output to prevent flicker. The higher popup's redraw will + // output the final blended result. + // Also suppress if this is a wide character whose second cell + // is under an opacity popup. + if (popup_is_under_opacity(row, col) + || (enc_utf8 && ScreenLinesUC[off] != 0 + && utf_char2cells(ScreenLinesUC[off]) == 2 + && col + 1 < screen_Columns + && popup_is_under_opacity(row, col + 1))) + { + screen_cur_col = 9999; + return; + } +#endif + // Outputting a character in the last cell on the screen may scroll the // screen up. Only do it when the "xn" termcap property is set, otherwise // mark the character invalid (update it when scrolled up). @@ -2331,6 +2360,15 @@ screen_char_2(unsigned off, int row, int col) return; } +#ifdef FEAT_PROP_POPUP + // If under a higher-zindex opacity popup, suppress output. + if (popup_is_under_opacity(row, col)) + { + screen_cur_col = 9999; + return; + } +#endif + // Output the first byte normally (positions the cursor), then write the // second byte directly. screen_char(off, row, col); @@ -2498,7 +2536,15 @@ screen_fill( && (attr == 0 || (norm_term && attr <= HL_ALL - && ((attr & ~(HL_BOLD | HL_ITALIC)) == 0)))) + && ((attr & ~(HL_BOLD | HL_ITALIC)) == 0))) +#ifdef FEAT_PROP_POPUP + // Do not use T_CE optimization if any cell in the + // range is under an opacity popup. The clear-to-eol + // command would erase the popup area on screen. + && !popup_is_under_opacity_range(row, + start_col, end_col) +#endif + ) { /* * check if we really need to clear something @@ -3461,6 +3507,17 @@ windgoto(int row, int col) break; } } +#ifdef FEAT_PROP_POPUP + // Don't output characters over opacity popup cells, it + // would show unblended background values. + if (cost < 999) + for (i = wouldbe_col; i < col; ++i) + if (popup_is_under_opacity(row, i)) + { + cost = 999; + break; + } +#endif } /* diff --git a/src/testdir/dumps/Test_popupwin_opacity_wide_1.dump b/src/testdir/dumps/Test_popupwin_opacity_wide_1.dump index 9ec6d5ba6..be464f772 100644 --- a/src/testdir/dumps/Test_popupwin_opacity_wide_1.dump +++ b/src/testdir/dumps/Test_popupwin_opacity_wide_1.dump @@ -1,8 +1,8 @@ >い*0&#ffffff0|え|ー@15|い|!+&| |1| @3 |い*&|え|ー@15|い|!+&| |2| @3 |い*&|え|ー@15|い|!+&| |3| @3 -|い*&|え*0#ffffff16#e000002|ー@6| |ー*0#0000000#ffffff0@7|い|!+&| |4| @3 -|い*&| +0#ffffff16#e000002|カ*&|ラ|フ|ル|な| +&|ー*&@1| |ー*0#0000000#ffffff0@7|い|!+&| |5| @3 +|い*&|え*0#ffffff16#e000002|ー@6| +&| +0#0000000#ffffff0|ー*&@7|い|!+&| |4| @3 +|い*&| +0#ffffff16#e000002|カ*&|ラ|フ|ル|な| +&|ー*&@1| +&| +0#0000000#ffffff0|ー*&@7|い|!+&| |5| @3 |い*&| +0#ffffff16#e000002|ポ*&|ッ|プ|ア|ッ|プ|で|─+&|╮| +0#0000000#ffffff0|ー*&@7|い|!+&| |6| @3 |い*&| +0#ffffff16#e000002|最*&|上|川| +&|ぼ*&|赤|い|な|│+&| +0#0000000#ffffff0|ー*&@7|い|!+&| |7| @3 |い*&| +0#ffffff16#e000002|│|あ*&|い|う|え|お|ー@1|│+&| +0#0000000#ffffff0|ー*&@7|い|!+&| |8| @3 diff --git a/src/version.c b/src/version.c index 295f8ed0b..d7c480a67 100644 --- a/src/version.c +++ b/src/version.c @@ -734,6 +734,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 189, /**/ 188, /**/ -- -- You received this message from the "vim_dev" maillist. Do not top-post! Type your reply below the text you are replying to. For more information, visit http://www.vim.org/maillist.php --- You received this message because you are subscribed to the Google Groups "vim_dev" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To view this discussion visit https://groups.google.com/d/msgid/vim_dev/E1w2bWG-00AU3b-07%40256bit.org.
