Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions runtime/doc/popup.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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|

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 'termguicolors' to be enabled. 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
Expand Down
3 changes: 3 additions & 0 deletions src/globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -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[]
Expand Down
195 changes: 195 additions & 0 deletions src/highlight.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading