]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0189: MS-Windows: opacity popups flicker during redraw in the console v9.2.0189
authorYasuhiro Matsumoto <mattn.jp@gmail.com>
Tue, 17 Mar 2026 20:51:22 +0000 (20:51 +0000)
committerChristian Brabandt <cb@256bit.org>
Tue, 17 Mar 2026 20:51:22 +0000 (20:51 +0000)
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 <mattn.jp@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
src/popupwin.c
src/proto/popupwin.pro
src/screen.c
src/testdir/dumps/Test_popupwin_opacity_wide_1.dump
src/version.c

index 23588c7ca48e70a3218eb2455e1f39fd0542e799..fe06777f7ccd02799690c31322e5acbc5df396ee 100644 (file)
@@ -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;
index ee8efbb142c4811dd9b08eface54206a76865650..d6ac2b72a0e68ebec166d26124a72ce68d37cff4 100644 (file)
@@ -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);
index acef4d38bd67eaa01e3e7ffed1e7a2e6c16cff5f..48261009f50affe8e9406e6fb8a98a18b8710806 100644 (file)
@@ -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
        }
 
        /*
index 9ec6d5ba63f6de69258ee11186f6fb3932d8027c..be464f7721ae5b040d0195870e2e0449d5f050ad 100644 (file)
@@ -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
index 295f8ed0bb3f313ecef670d1b326e053177f5d6a..d7c480a67cd47e2f584ca07dda7be1761ff5fe51 100644 (file)
@@ -734,6 +734,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    189,
 /**/
     188,
 /**/