From: Yasuhiro Matsumoto Date: Wed, 18 Feb 2026 17:58:54 +0000 (+0000) Subject: patch 9.2.0017: popup: cannot allow to specify transparency X-Git-Tag: v9.2.0017^0 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=95e8faa24f944b9f395089c38948aef9722750ec;p=thirdparty%2Fvim.git patch 9.2.0017: popup: cannot allow to specify transparency Problem: popup: Popup windows do not support a transparency setting. Solution: Add the "opacity" option to popup windows to support transparency when using the GUI or 'termguicolors' (Yasuhiro Matsumoto). closes: #19272 Signed-off-by: Yasuhiro Matsumoto Signed-off-by: Christian Brabandt --- diff --git a/runtime/doc/popup.txt b/runtime/doc/popup.txt index 5f7d7ab531..021410eb2c 100644 --- a/runtime/doc/popup.txt +++ b/runtime/doc/popup.txt @@ -1,4 +1,4 @@ -*popup.txt* For Vim version 9.2. Last change: 2026 Feb 14 +*popup.txt* For Vim version 9.2. Last change: 2026 Feb 18 VIM REFERENCE MANUAL by Bram Moolenaar @@ -21,6 +21,7 @@ Displaying text in a popup window. *popup* *popup-window* *popupwin* Popup filter |popup-filter| Popup callback |popup-callback| Popup scrollbar |popup-scrollbar| + Popup opacity |popup-opacity| Popup mask |popup-mask| 4. Examples |popup-examples| @@ -772,6 +773,13 @@ The second argument of |popup_create()| is a dictionary with options: the popup window. highlight Highlight group name to use for the text, stored in the 'wincolor' option. + opacity Opacity of the popup, a value between 0 and 100: + 0 is fully transparent (background text fully visible) + 100 is fully opaque (default, no transparency) + Values in between blend the popup background with the + underlying text, making it partially transparent. + Requires 'termguicolors' to be set. + Also see |popup-opacity|. padding List with numbers, defining the padding above/right/below/left of the popup (similar to CSS). An empty list uses a padding of 1 all around. The @@ -1046,6 +1054,42 @@ A click in the lower half will scroll the text up one line. However, this is limited so that the popup does not get smaller. + +POPUP OPACITY *popup-opacity* + +A popup window can be made semi-transparent by setting the "opacity" option. +The opacity value ranges from 0 to 100: + 0 Fully transparent - the popup background is invisible and the + text behind the popup is fully visible. + 100 Fully opaque (default) - the popup is not transparent at all. + 1-99 Partially transparent - the popup background is blended with + the underlying text, making both partially visible. + +The transparency effect requires using the GUI or having 'termguicolors' +enabled in the terminal. Without it, the opacity setting has no effect. + +When a popup is transparent: +- The popup's background color is blended with the background text +- The popup's text (foreground) remains fully visible and unblended +- Text behind the popup is visible through transparent areas +- The more transparent the popup (lower opacity), the more clearly the + background text can be seen + +This can be useful for: +- Creating overlay windows that don't completely obscure underlying text +- Showing contextual information without blocking the view +- Creating visual effects and modern UI designs + +Example with 50% opacity: > + let winid = popup_create('Semi-transparent text', #{ + \ line: 5, + \ col: 10, + \ opacity: 50, + \ }) + +The opacity can be changed dynamically using |popup_setoptions()|: > + call popup_setoptions(winid, #{opacity: 80}) + POPUP MASK *popup-mask* To minimize the text that the popup covers, parts of it can be made diff --git a/runtime/doc/tags b/runtime/doc/tags index b041d00a38..2c7d10766e 100644 --- a/runtime/doc/tags +++ b/runtime/doc/tags @@ -9771,6 +9771,7 @@ popup-mapping popup.txt /*popup-mapping* popup-mask popup.txt /*popup-mask* popup-menu gui.txt /*popup-menu* popup-menu-added version5.txt /*popup-menu-added* +popup-opacity popup.txt /*popup-opacity* popup-position popup.txt /*popup-position* popup-props popup.txt /*popup-props* popup-scrollbar popup.txt /*popup-scrollbar* diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt index d3362f6c84..09a3c8c161 100644 --- a/runtime/doc/version9.txt +++ b/runtime/doc/version9.txt @@ -52582,6 +52582,10 @@ VERSION 9.3 *version-9.3* *version9.3* *vim-9.3* This section is about improvements made between version 9.2 and 9.3 and is work in progress. +Popups ~ +------ +- Support for transparency, see |popup-opacity|. + *changed-9.3* Changed~ ------- diff --git a/src/globals.h b/src/globals.h index 5e96304cc3..4b40873a96 100644 --- a/src/globals.h +++ b/src/globals.h @@ -120,6 +120,9 @@ EXTERN tabpage_T *popup_mask_tab INIT(= NULL); // Zindex in for screen_char(): if lower than the value in "popup_mask" // drawing the character is skipped. EXTERN int screen_zindex INIT(= 0); + +// Currently drawing popup with opacity window, or NULL. +EXTERN win_T *screen_opacity_popup INIT(= NULL); #endif EXTERN int screen_Rows INIT(= 0); // actual size of ScreenLines[] diff --git a/src/highlight.c b/src/highlight.c index 9afe69c4dd..5df56b1001 100644 --- a/src/highlight.c +++ b/src/highlight.c @@ -3081,6 +3081,201 @@ hl_combine_attr(int char_attr, int prim_attr) return get_attr_entry(&term_attr_table, &new_en); } +#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) +/* + * Blend two RGB colors based on blend value (0-100). + * blend: 0=use popup color, 100=use background color + * If bg_color is INVALCOLOR, high blend means more visible (return INVALCOLOR). + */ + static guicolor_T +blend_colors(guicolor_T popup_color, guicolor_T bg_color, int blend_val) +{ + int r1, g1, b1, r2, g2, b2, r, g, b; + + if (popup_color == INVALCOLOR) + return INVALCOLOR; + + r1 = (popup_color >> 16) & 0xFF; + g1 = (popup_color >> 8) & 0xFF; + b1 = popup_color & 0xFF; + + if (bg_color == INVALCOLOR) + { + // Background color unknown: fade popup color to black as blend increases + // This makes background text more visible at high blend values + r = r1 * (100 - blend_val) / 100; + g = g1 * (100 - blend_val) / 100; + b = b1 * (100 - blend_val) / 100; + return (r << 16) | (g << 8) | b; + } + + r2 = (bg_color >> 16) & 0xFF; + g2 = (bg_color >> 8) & 0xFF; + b2 = bg_color & 0xFF; + + r = r1 + (r2 - r1) * blend_val / 100; + g = g1 + (g2 - g1) * blend_val / 100; + b = b1 + (b2 - b1) * blend_val / 100; + + return (r << 16) | (g << 8) | b; +} +#endif + +/* + * Blend attributes for popup windows with opacity. + * Blends foreground and/or background colors based on blend value (0-100). + * blend: 0 = opaque (use popup colors), 100 = transparent (use background colors) + * blend_fg: TRUE to blend foreground color, FALSE to keep popup foreground + */ + int +hl_blend_attr(int char_attr, int popup_attr, int blend, int blend_fg UNUSED) +{ + attrentry_T *char_aep = NULL; + attrentry_T *popup_aep; + attrentry_T new_en; + + // If both attrs are 0, return 0 + if (char_attr == 0 && popup_attr == 0) + return 0; + if (blend >= 100) + return char_attr; // Fully transparent, show background only + +#ifdef FEAT_GUI + if (gui.in_use) + { + if (char_attr > HL_ALL) + char_aep = syn_gui_attr2entry(char_attr); + if (char_aep != NULL) + new_en = *char_aep; + else + { + CLEAR_FIELD(new_en); + new_en.ae_u.gui.fg_color = INVALCOLOR; + new_en.ae_u.gui.bg_color = INVALCOLOR; + new_en.ae_u.gui.sp_color = INVALCOLOR; + if (char_attr <= HL_ALL) + new_en.ae_attr = char_attr; + } + + if (popup_attr > HL_ALL) + { + popup_aep = syn_gui_attr2entry(popup_attr); + if (popup_aep != NULL) + { + if (blend_fg) + { + // blend_fg=TRUE: blend bg text fg from popup bg color to white + // At blend=0: fg becomes popup bg (blue, invisible - opaque popup) + // At blend=100: fg is white (visible - transparent popup) + // Always use white (0xFFFFFF) as the target color for consistency + if (popup_aep->ae_u.gui.bg_color != INVALCOLOR) + { + new_en.ae_u.gui.fg_color = blend_colors( + popup_aep->ae_u.gui.bg_color, 0xFFFFFF, blend); + } + } + else if (popup_aep->ae_u.gui.fg_color != INVALCOLOR) + { + // blend_fg=FALSE: use popup foreground + new_en.ae_u.gui.fg_color = popup_aep->ae_u.gui.fg_color; + } + // Blend background color + if (popup_aep->ae_u.gui.bg_color != INVALCOLOR) + { + // Always use popup background, fade to black based on blend + int r = ((popup_aep->ae_u.gui.bg_color >> 16) & 0xFF) * (100 - blend) / 100; + int g = ((popup_aep->ae_u.gui.bg_color >> 8) & 0xFF) * (100 - blend) / 100; + int b = (popup_aep->ae_u.gui.bg_color & 0xFF) * (100 - blend) / 100; + new_en.ae_u.gui.bg_color = (r << 16) | (g << 8) | b; + } + } + } + return get_attr_entry(&gui_attr_table, &new_en); + } +#endif + + if (IS_CTERM) + { + if (char_attr > HL_ALL) + char_aep = syn_cterm_attr2entry(char_attr); + if (char_aep != NULL) + new_en = *char_aep; + else + { + CLEAR_FIELD(new_en); +#ifdef FEAT_TERMGUICOLORS + new_en.ae_u.cterm.bg_rgb = INVALCOLOR; + new_en.ae_u.cterm.fg_rgb = INVALCOLOR; + new_en.ae_u.cterm.ul_rgb = INVALCOLOR; +#endif + if (char_attr <= HL_ALL) + new_en.ae_attr = char_attr; + } + + if (popup_attr > HL_ALL) + { + popup_aep = syn_cterm_attr2entry(popup_attr); + if (popup_aep != NULL) + { + // Blend foreground color + if (popup_aep->ae_u.cterm.fg_color > 0) + { + if (new_en.ae_u.cterm.fg_color > 0) + new_en.ae_u.cterm.fg_color = popup_aep->ae_u.cterm.fg_color; + else + new_en.ae_u.cterm.fg_color = popup_aep->ae_u.cterm.fg_color; + } + // Use popup background color (cterm colors don't support blending) + if (popup_aep->ae_u.cterm.bg_color > 0) + { + new_en.ae_u.cterm.bg_color = popup_aep->ae_u.cterm.bg_color; + } +#ifdef FEAT_TERMGUICOLORS + // Blend RGB colors for termguicolors mode + if (blend_fg) + { + // blend_fg=TRUE: blend bg text fg from popup bg color to white + // Always use white (0xFFFFFF) as the target color for consistency + if (popup_aep->ae_u.cterm.bg_rgb != INVALCOLOR) + { + new_en.ae_u.cterm.fg_rgb = blend_colors( + popup_aep->ae_u.cterm.bg_rgb, 0xFFFFFF, blend); + } + } + else if (popup_aep->ae_u.cterm.fg_rgb != INVALCOLOR) + { + // blend_fg=FALSE: use popup foreground + new_en.ae_u.cterm.fg_rgb = popup_aep->ae_u.cterm.fg_rgb; + } + if (popup_aep->ae_u.cterm.bg_rgb != INVALCOLOR) + { + // Always use popup background, fade to black based on blend + int r = ((popup_aep->ae_u.cterm.bg_rgb >> 16) & 0xFF) * (100 - blend) / 100; + int g = ((popup_aep->ae_u.cterm.bg_rgb >> 8) & 0xFF) * (100 - blend) / 100; + int b = (popup_aep->ae_u.cterm.bg_rgb & 0xFF) * (100 - blend) / 100; + new_en.ae_u.cterm.bg_rgb = (r << 16) | (g << 8) | b; + } +#endif + } + } + return get_attr_entry(&cterm_attr_table, &new_en); + } + + if (char_attr > HL_ALL) + char_aep = syn_term_attr2entry(char_attr); + if (char_aep != NULL) + new_en = *char_aep; + else + { + CLEAR_FIELD(new_en); + if (char_attr <= HL_ALL) + new_en.ae_attr = char_attr; + } + + // For term mode, no separate background color handling. + return get_attr_entry(&term_attr_table, &new_en); +} + #ifdef FEAT_GUI attrentry_T * syn_gui_attr2entry(int attr) diff --git a/src/popupwin.c b/src/popupwin.c index a126822d0d..380b884be2 100644 --- a/src/popupwin.c +++ b/src/popupwin.c @@ -781,6 +781,24 @@ apply_general_options(win_T *wp, dict_T *dict) wp->w_popup_flags &= ~POPF_RESIZE; } + di = dict_find(dict, (char_u *)"opacity", -1); + if (di != NULL) + { + nr = dict_get_number(dict, "opacity"); + if (nr > 0 && nr <= 100) + { + // opacity: 0-100, where 0=transparent, 100=opaque + // Convert to blend (0=opaque, 100=transparent) + wp->w_popup_flags |= POPF_OPACITY; + wp->w_popup_blend = 100 - nr; + } + else + { + wp->w_popup_flags &= ~POPF_OPACITY; + wp->w_popup_blend = 0; + } + } + di = dict_find(dict, (char_u *)"close", -1); if (di != NULL) { @@ -3249,6 +3267,9 @@ f_popup_setoptions(typval_T *argvars, typval_T *rettv UNUSED) int id; win_T *wp; linenr_T old_firstline; +#ifdef FEAT_PROP_POPUP + int old_blend; +#endif if (in_vim9script() && (check_for_number_arg(argvars, 0) == FAIL @@ -3264,11 +3285,23 @@ f_popup_setoptions(typval_T *argvars, typval_T *rettv UNUSED) return; dict = argvars[1].vval.v_dict; old_firstline = wp->w_firstline; +#ifdef FEAT_PROP_POPUP + old_blend = wp->w_popup_blend; +#endif (void)apply_options(wp, dict, FALSE); if (old_firstline != wp->w_firstline) redraw_win_later(wp, UPD_NOT_VALID); +#ifdef FEAT_PROP_POPUP + // Force redraw if opacity value changed + if (old_blend != wp->w_popup_blend) + { + redraw_win_later(wp, UPD_NOT_VALID); + // Also redraw windows below the popup + redraw_all_later(UPD_NOT_VALID); + } +#endif popup_adjust_position(wp); } @@ -3518,6 +3551,9 @@ f_popup_getoptions(typval_T *argvars, typval_T *rettv) dict_add_number(dict, "resize", (wp->w_popup_flags & POPF_RESIZE) != 0); dict_add_number(dict, "posinvert", (wp->w_popup_flags & POPF_POSINVERT) != 0); + // Return opacity (0-100) by converting from internal blend value + dict_add_number(dict, "opacity", + (wp->w_popup_flags & POPF_OPACITY) ? 100 - wp->w_popup_blend : 100); dict_add_number(dict, "cursorline", (wp->w_popup_flags & POPF_CURSORLINE) != 0); dict_add_string(dict, "highlight", wp->w_p_wcr); @@ -4088,6 +4124,12 @@ may_update_popup_mask(int type) width = popup_width(wp); height = popup_height(wp); popup_update_mask(wp, width, height); + + // Popup with opacitys do not block lower layers from drawing, + // so they don't participate in the popup_mask. + if (wp->w_popup_flags & POPF_OPACITY) + continue; + for (line = wp->w_winrow; line < wp->w_winrow + height && line < screen_Rows; ++line) for (col = wp->w_wincol; @@ -4195,14 +4237,62 @@ may_update_popup_position(void) } /* - * Return a string of "len" spaces in IObuff. + * Draw a single padding cell with opacity blending. + * Restores background from saved data and blends with popup attribute. */ - static char_u * -get_spaces(int len) + static void +draw_opacity_padding_cell( + int row, + int col, + schar_T *saved_screenlines, + int *saved_screenattrs, + u8char_T *saved_screenlinesuc, + int save_start_row, + int save_start_col, + int save_rows, + int save_cols) +{ + int off = LineOffset[row] + col; + int r = row - save_start_row; + int c = col - save_start_col; + + if (r >= 0 && r < save_rows && c >= 0 && c < save_cols) + { + int save_off = r * save_cols + c; + ScreenLines[off] = saved_screenlines[save_off]; + ScreenAttrs[off] = saved_screenattrs[save_off]; + if (enc_utf8 && saved_screenlinesuc != NULL) + ScreenLinesUC[off] = saved_screenlinesuc[save_off]; + int popup_attr_val = get_wcr_attr(screen_opacity_popup); + int blend = screen_opacity_popup->w_popup_blend; + ScreenAttrs[off] = hl_blend_attr(ScreenAttrs[off], + popup_attr_val, blend, TRUE); + screen_char(off, row, col); + } +} + +/* + * Fill a rectangular padding area with opacity blending. + */ + static void +fill_opacity_padding( + int start_row, + int end_row, + int start_col, + int end_col, + schar_T *saved_screenlines, + int *saved_screenattrs, + u8char_T *saved_screenlinesuc, + int save_start_row, + int save_start_col, + int save_rows, + int save_cols) { - vim_memset(IObuff, ' ', (size_t)len); - IObuff[len] = NUL; - return IObuff; + for (int pad_row = start_row; pad_row < end_row; pad_row++) + for (int pad_col = start_col; pad_col < end_col; pad_col++) + draw_opacity_padding_cell(pad_row, pad_col, + saved_screenlines, saved_screenattrs, saved_screenlinesuc, + save_start_row, save_start_col, save_rows, save_cols); } /* @@ -4249,6 +4339,64 @@ update_popups(void (*win_update)(win_T *wp)) // zindex is on top of the character. screen_zindex = wp->w_zindex; + // Set popup with opacity context for screen drawing. + if (wp->w_popup_flags & POPF_OPACITY) + screen_opacity_popup = wp; + else + screen_opacity_popup = NULL; + + // Save background ScreenLines for padding opacity. + // We need to save it before win_update() overwrites it. + schar_T *saved_screenlines = NULL; + int *saved_screenattrs = NULL; + u8char_T *saved_screenlinesuc = NULL; + int save_start_row = 0; + int save_start_col = 0; + int save_rows = 0; + int save_cols = 0; + + if (screen_opacity_popup != NULL + && (wp->w_popup_padding[0] > 0 || wp->w_popup_padding[1] > 0 + || wp->w_popup_padding[2] > 0 || wp->w_popup_padding[3] > 0)) + { + // Calculate the area to save (all padding regions including top/bottom) + save_start_row = wp->w_winrow + wp->w_popup_border[0]; + save_start_col = wp->w_wincol + wp->w_popup_border[3]; + save_rows = wp->w_popup_padding[0] + wp->w_height + wp->w_popup_padding[2]; + save_cols = wp->w_popup_padding[3] + wp->w_width + wp->w_popup_padding[1]; + + // Allocate buffers + saved_screenlines = ALLOC_MULT(schar_T, save_rows * save_cols); + saved_screenattrs = ALLOC_MULT(int, save_rows * save_cols); + if (enc_utf8) + saved_screenlinesuc = ALLOC_MULT(u8char_T, save_rows * save_cols); + + // Save the background + if (saved_screenlines != NULL && saved_screenattrs != NULL) + { + for (int r = 0; r < save_rows; r++) + { + int screen_row = save_start_row + r; + if (screen_row >= 0 && screen_row < screen_Rows) + { + for (int c = 0; c < save_cols; c++) + { + int screen_col = save_start_col + c; + if (screen_col >= 0 && screen_col < screen_Columns) + { + int off = LineOffset[screen_row] + screen_col; + int save_off = r * save_cols + c; + saved_screenlines[save_off] = ScreenLines[off]; + saved_screenattrs[save_off] = ScreenAttrs[off]; + if (enc_utf8 && saved_screenlinesuc != NULL) + saved_screenlinesuc[save_off] = ScreenLinesUC[off]; + } + } + } + } + } + } + // Set flags in popup_transparent[] for masked cells. update_popup_transparent(wp, 1); @@ -4334,8 +4482,18 @@ update_popups(void (*win_update)(win_T *wp)) border_attr[i] = popup_attr; if (wp->w_border_highlight[i] != NULL) border_attr[i] = syn_name2attr(wp->w_border_highlight[i]); + + // Apply blend to border attributes for popup with opacitys + if ((wp->w_popup_flags & POPF_OPACITY) && wp->w_popup_blend > 0) + border_attr[i] = hl_blend_attr(0, border_attr[i], + wp->w_popup_blend, FALSE); } + // Apply blend to popup_attr for padding areas + if ((wp->w_popup_flags & POPF_OPACITY) && wp->w_popup_blend > 0) + popup_attr = hl_blend_attr(0, popup_attr, wp->w_popup_blend, FALSE); + + // Title goes on top of border or padding. title_wincol = wp->w_wincol + 1; if (wp->w_popup_title != NULL) @@ -4416,15 +4574,43 @@ update_popups(void (*win_update)(win_T *wp)) if (title_len > 0 && row == wp->w_winrow) { // top padding and no border; do not draw over the title - screen_fill(row, row + 1, padcol, title_wincol, - ' ', ' ', popup_attr); - screen_fill(row, row + 1, title_wincol + title_len, - padendcol, ' ', ' ', popup_attr); + if (screen_opacity_popup != NULL && saved_screenlines != NULL) + { + // Left of title + fill_opacity_padding(row, row + 1, padcol, title_wincol, + saved_screenlines, saved_screenattrs, + saved_screenlinesuc, save_start_row, save_start_col, + save_rows, save_cols); + // Right of title + fill_opacity_padding(row, row + 1, + title_wincol + title_len, padendcol, + saved_screenlines, saved_screenattrs, + saved_screenlinesuc, save_start_row, save_start_col, + save_rows, save_cols); + } + else + { + screen_fill(row, row + 1, padcol, title_wincol, + ' ', ' ', popup_attr); + screen_fill(row, row + 1, title_wincol + title_len, + padendcol, ' ', ' ', popup_attr); + } row += 1; top_padding -= 1; } - screen_fill(row, row + top_padding, padcol, padendcol, - ' ', ' ', popup_attr); + // Draw remaining top padding rows + if (screen_opacity_popup != NULL && saved_screenlines != NULL) + { + fill_opacity_padding(row, row + top_padding, padcol, padendcol, + saved_screenlines, saved_screenattrs, + saved_screenlinesuc, save_start_row, save_start_col, + save_rows, save_cols); + } + else + { + screen_fill(row, row + top_padding, padcol, padendcol, + ' ', ' ', popup_attr); + } } // Compute scrollbar thumb position and size. @@ -4495,7 +4681,8 @@ update_popups(void (*win_update)(win_T *wp)) col = 0; } if (pad_left > 0) - screen_puts(get_spaces(pad_left), row, col, popup_attr); + screen_fill(row, row + 1, col, col + pad_left, + ' ', ' ', popup_attr); } // scrollbar if (wp->w_has_scrollbar) @@ -4520,10 +4707,14 @@ update_popups(void (*win_update)(win_T *wp)) } // right padding if (do_padding && wp->w_popup_padding[1] > 0) - screen_puts(get_spaces(wp->w_popup_padding[1]), row, - wincol + wp->w_popup_border[3] - + wp->w_popup_padding[3] + wp->w_width + wp->w_leftcol, - popup_attr); + { + int pad_col_start = wincol + wp->w_popup_border[3] + + wp->w_popup_padding[3] + wp->w_width + wp->w_leftcol; + int pad_col_end = pad_col_start + wp->w_popup_padding[1]; + + screen_fill(row, row + 1, pad_col_start, pad_col_end, + ' ', ' ', popup_attr); + } } // right shadow @@ -4543,8 +4734,14 @@ update_popups(void (*win_update)(win_T *wp)) // bottom padding row = wp->w_winrow + wp->w_popup_border[0] + wp->w_popup_padding[0] + wp->w_height; - screen_fill(row, row + wp->w_popup_padding[2], - padcol, padendcol, ' ', ' ', popup_attr); + if (screen_opacity_popup != NULL && saved_screenlines != NULL) + fill_opacity_padding(row, row + wp->w_popup_padding[2], + padcol, padendcol, saved_screenlines, saved_screenattrs, + saved_screenlinesuc, save_start_row, save_start_col, + save_rows, save_cols); + else + screen_fill(row, row + wp->w_popup_padding[2], + padcol, padendcol, ' ', ' ', popup_attr); } if (wp->w_popup_border[2] > 0) @@ -4583,6 +4780,17 @@ update_popups(void (*win_update)(win_T *wp)) update_popup_transparent(wp, 0); + // Free saved background data + if (saved_screenlines != NULL) + vim_free(saved_screenlines); + if (saved_screenattrs != NULL) + vim_free(saved_screenattrs); + if (saved_screenlinesuc != NULL) + vim_free(saved_screenlinesuc); + + // Clear popup with opacity context. + screen_opacity_popup = NULL; + // Back to the normal zindex. screen_zindex = 0; diff --git a/src/proto/highlight.pro b/src/proto/highlight.pro index 760e8fa129..908765c217 100644 --- a/src/proto/highlight.pro +++ b/src/proto/highlight.pro @@ -20,6 +20,7 @@ int get_tgc_attr_idx(int attr, guicolor_T fg, guicolor_T bg); int get_gui_attr_idx(int attr, guicolor_T fg, guicolor_T bg); void clear_hl_tables(void); int hl_combine_attr(int char_attr, int prim_attr); +int hl_blend_attr(int char_attr, int popup_attr, int blend, int blend_fg); attrentry_T *syn_gui_attr2entry(int attr); int syn_attr2attr(int attr); attrentry_T *syn_term_attr2entry(int attr); diff --git a/src/screen.c b/src/screen.c index b92e2779fc..74e4a40e37 100644 --- a/src/screen.c +++ b/src/screen.c @@ -583,6 +583,7 @@ screen_line( redraw_this = TRUE; } #endif + // skip the second cell for double-width characters. if (redraw_this && char_cells == 2 && skip_for_popup(row, col + coloff + 1)) redraw_this = FALSE; // For transparent popup cells, update the background character @@ -613,6 +614,71 @@ screen_line( else screen_char(off_to, row, col + coloff); } + +#ifdef FEAT_PROP_POPUP + // For popup with opacity windows: if drawing a space, show the + // underlying character with the popup's attributes blended in. + int opacity_blank = FALSE; + // Check if the popup is drawing a space and the background is the + // second cell of a wide character. Skip drawing to preserve the + // wide character that was drawn in the previous cell. + if (screen_opacity_popup != NULL + && (flags & SLF_POPUP) + && ScreenLines[off_from] == ' ' + && (!enc_utf8 || ScreenLinesUC[off_from] == 0) + && ScreenLines[off_to] == 0 + && off_to > 0 + && enc_utf8 && ScreenLinesUC[off_to - 1] != 0 + && utf_char2cells(ScreenLinesUC[off_to - 1]) == 2) + { + // This is the second cell of a wide character. Don't overwrite it. + opacity_blank = TRUE; + redraw_this = FALSE; + } + else if (screen_opacity_popup != NULL + && (flags & SLF_POPUP) + && ScreenLines[off_from] == ' ' + && (!enc_utf8 || ScreenLinesUC[off_from] == 0) + && ScreenLines[off_to] != 0) + { + int bg_char_cells = 1; + if (enc_utf8 && ScreenLinesUC[off_to] != 0) + bg_char_cells = utf_char2cells(ScreenLinesUC[off_to]); + + // For wide background character, check if the next popup cell + // is also a space. If not, the wide char would be partially + // covered by a popup character, so don't show it. + if (bg_char_cells == 2) + { + if (col + 1 >= endcol || off_from + 1 >= max_off_from + || off_to + 1 >= max_off_to) + // At the edge of the screen, skip wide char. + goto skip_opacity; + int next_off_from = off_from + 1; + if (!(ScreenLines[next_off_from] == ' ' + && (!enc_utf8 || ScreenLinesUC[next_off_from] == 0))) + { + // Next cell is not a space, don't show the wide char. + goto skip_opacity; + } + } + + opacity_blank = TRUE; + // Keep the underlying character and blend its foreground color + // from popup background color to original color. + int popup_attr = get_wcr_attr(screen_opacity_popup); + int blend = screen_opacity_popup->w_popup_blend; + ScreenAttrs[off_to] = hl_blend_attr(ScreenAttrs[off_to], + popup_attr, blend, TRUE); + screen_char(off_to, row, col + coloff); + // For wide background character, also update the second cell. + if (bg_char_cells == 2) + ScreenAttrs[off_to + 1] = ScreenAttrs[off_to]; + redraw_this = FALSE; + } +skip_opacity: +#endif + if (redraw_this) { /* @@ -746,11 +812,24 @@ screen_line( redraw_next = TRUE; #endif ScreenAttrs[off_to] = ScreenAttrs[off_from]; +#ifdef FEAT_PROP_POPUP + // For popup with opacity text: blend background with default (0) + if (screen_opacity_popup != NULL + && (flags & SLF_POPUP) + && screen_opacity_popup->w_popup_blend > 0) + { + int popup_attr = get_wcr_attr(screen_opacity_popup); + int blend = screen_opacity_popup->w_popup_blend; + // Blend popup attr with default background (0) + // FALSE = keep popup foreground color, blend background only + ScreenAttrs[off_to] = hl_blend_attr(0, popup_attr, blend, FALSE); + } +#endif // For simplicity set the attributes of second half of a // double-wide character equal to the first half. if (char_cells == 2) - ScreenAttrs[off_to + 1] = ScreenAttrs[off_from]; + ScreenAttrs[off_to + 1] = ScreenAttrs[off_to]; if (enc_dbcs != 0 && char_cells == 2) screen_char_2(off_to, row, col + coloff); @@ -775,9 +854,14 @@ screen_line( screen_stop_highlight(); } - ScreenCols[off_to] = ScreenCols[off_from]; - if (char_cells == 2) - ScreenCols[off_to + 1] = ScreenCols[off_from + 1]; +#ifdef FEAT_PROP_POPUP + if (!opacity_blank) +#endif + { + ScreenCols[off_to] = ScreenCols[off_from]; + if (char_cells == 2) + ScreenCols[off_to + 1] = ScreenCols[off_from + 1]; + } off_to += char_cells; off_from += char_cells; @@ -1725,6 +1809,9 @@ screen_start_highlight(int attr) { if (aep->ae_u.cterm.fg_rgb != INVALCOLOR) term_fg_rgb_color(aep->ae_u.cterm.fg_rgb); + else + // Reset to default foreground color (SGR 39) + out_str((char_u *)"\033[39m"); } else #endif @@ -2310,6 +2397,51 @@ screen_fill( // Skip if under a(nother) popup. && !skip_for_popup(row, col)) { +#ifdef FEAT_PROP_POPUP + // For popup with opacity: show underlying character with + // popup's background color applied. + if (screen_opacity_popup != NULL && c == ' ') + { + // Skip if background is the second cell of a wide character. + // Check if previous cell is a wide character. + if (ScreenLines[off] == 0 + && off > 0 + && enc_utf8 && ScreenLinesUC[off - 1] != 0 + && utf_char2cells(ScreenLinesUC[off - 1]) == 2) + goto next_col; + } + if (screen_opacity_popup != NULL && c == ' ' + && ScreenLines[off] != 0) + { + int bg_char_cells = 1; + if (enc_utf8 && ScreenLinesUC[off] != 0) + bg_char_cells = utf_char2cells(ScreenLinesUC[off]); + + // For wide background character, check if the next cell + // is also being filled with space. If not, the wide char + // would be partially covered, so don't show it. + if (bg_char_cells == 2) + { + if (col + 1 >= end_col) + // At the edge, skip wide char. + goto skip_opacity_fill; + // In screen_fill, we're filling with 'c' which is ' '. + // The next cell will also be filled with c2 (usually ' '). + // If c2 is not space, skip the wide char. + if (c2 != ' ') + goto skip_opacity_fill; + } + + int popup_attr = get_wcr_attr(screen_opacity_popup); + int blend = screen_opacity_popup->w_popup_blend; + // Blend both foreground and background for padding area + ScreenAttrs[off] = hl_blend_attr(ScreenAttrs[off], + popup_attr, blend, TRUE); + screen_char(off, row, col); + goto next_col; + } +skip_opacity_fill: +#endif #if defined(FEAT_GUI) || defined(UNIX) // The bold trick may make a single row of pixels appear in // the next character. When a bold character is removed, the @@ -2350,6 +2482,9 @@ screen_fill( if (!did_delete || c != ' ') screen_char(off, row, col); } +#ifdef FEAT_PROP_POPUP +next_col: +#endif ScreenCols[off] = -1; ++off; if (col == start_col) diff --git a/src/structs.h b/src/structs.h index df296b35d3..ed112e0644 100644 --- a/src/structs.h +++ b/src/structs.h @@ -4059,6 +4059,7 @@ struct window_S #ifdef FEAT_PROP_POPUP int w_popup_flags; // POPF_ values + int w_popup_blend; // 0-100: transparency level for popup with opacitys int w_popup_handled; // POPUP_HANDLE[0-9] flags char_u *w_popup_title; poppos_T w_popup_pos; diff --git a/src/version.c b/src/version.c index d6cc249c1f..fd53cbfb66 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 */ +/**/ + 17, /**/ 16, /**/ diff --git a/src/vim.h b/src/vim.h index bd8b4a5d67..3d375f0998 100644 --- a/src/vim.h +++ b/src/vim.h @@ -705,6 +705,7 @@ extern int (*dyn_libintl_wputenv)(const wchar_t *envstring); #define POPF_INFO 0x200 // used for info of popup menu #define POPF_INFO_MENU 0x400 // align info popup with popup menu #define POPF_POSINVERT 0x800 // vertical position can be inverted +#define POPF_OPACITY 0x1000 // popup has opacity/transparency setting // flags used in w_popup_handled #define POPUP_HANDLED_1 0x01 // used by mouse_find_win()