if (wp->w_height < 0)
wp->w_height = 0;
if (cl->clip_top_content > 0)
- {
+ // w_winrow already points at the first visible row.
wp->w_topline += cl->clip_top_content;
- wp->w_winrow += cl->clip_top_content;
- }
}
if (wp->w_popup_leftclip > 0 || wp->w_popup_rightclip > 0)
{
return;
}
+ // Make w_winrow the first visible screen row (>= 0); the clipped-off top
+ // rows are recorded in w_popup_topoff.
+ wp->w_winrow += wp->w_popup_topoff;
+
#ifdef FEAT_IMAGE_SIXEL
// Final winrow is now known: encode (or re-encode) the sixel image so it
// fits within the screen and does not trigger sixel-scroll on terminals.
width = popup_width(wp);
height = popup_height(wp);
+ // w_winrow/w_wincol are visible cells (>= 0); w_popup_topoff rows are
+ // clipped off the top.
for (r = wp->w_winrow;
- r < wp->w_winrow + height && r < screen_Rows; ++r)
+ r < wp->w_winrow + height - wp->w_popup_topoff
+ && r < screen_Rows; ++r)
for (c = wp->w_wincol;
c < wp->w_wincol + width - wp->w_popup_leftoff
&& c < screen_Columns; ++c)
continue;
{
- int mask_start = wp->w_winrow + wp->w_popup_topoff;
+ int mask_start = wp->w_winrow;
int mask_end = mask_start + height;
int mask_col_start = wp->w_wincol + wp->w_popup_leftclip;
int mask_col_end = wp->w_wincol + width - wp->w_popup_leftoff
- wp->w_popup_leftoff;
if (wp->w_wincol + left_extra < 0)
left_extra = -wp->w_wincol;
- wp->w_winrow += top_off;
+ // Move to the content top, skipping any clipped top border/padding.
+ wp->w_winrow += MAX(top_off - wp->w_popup_topoff, 0);
wp->w_wincol += left_extra;
// Draw the popup text, unless it's off screen.
wp->w_cursor.lnum = wp->w_botline - 1;
}
- wp->w_winrow -= top_off;
+ wp->w_winrow -= MAX(top_off - wp->w_popup_topoff, 0);
wp->w_wincol -= left_extra;
- // "clipwindow" with top-clip shifts all popup decorations down so the
- // first visible row of the popup lands at the host window's top edge.
- // Apply the shift before drawing borders/padding/etc. and restore at
- // the end of this popup's iteration.
- wp->w_winrow += wp->w_popup_topoff;
-
// Add offset for border and padding if not done already.
if ((wp->w_flags & WFLAG_WCOL_OFF_ADDED) == 0)
{
if (override_success)
pop_highlight_overrides();
- // Undo the topoff shift applied before drawing the borders so the
- // next iteration sees the popup's logical winrow.
- wp->w_winrow -= wp->w_popup_topoff;
-
#ifdef FEAT_IMAGE
// Emit the popup image right after this popup's decorations land in
// ScreenLines. Popups are walked in zindex order, so a higher
call prop_type_delete('clipprop')
endfunc
+func Test_popup_clipwindow_opacity_negative_winrow()
+ " A "clipwindow" popup with "opacity" whose textprop anchor scrolls above
+ " the window top must not index the opacity mask out of bounds.
+ call prop_type_add('clipprop', {})
+ new
+ call setline(1, range(1, 200)->mapnew({_, v -> 'line ' .. v}))
+ call prop_add(5, 1, #{type: 'clipprop', length: 5})
+ let host = win_getid()
+
+ let id = popup_create(['aaa', 'bbb', 'ccc', 'ddd', 'eee'], #{
+ \ textprop: 'clipprop',
+ \ textpropwin: host,
+ \ wrap: v:false,
+ \ fixed: v:true,
+ \ clipwindow: v:true,
+ \ opacity: 50,
+ \ })
+ call assert_true(id > 0)
+ redraw
+
+ " Scroll so the prop (line 5) sits a couple of lines above the top, so the
+ " popup is clipped at the host window's top edge.
+ call win_execute(host, 'normal! 8Gzt')
+ redraw
+ redraw
+
+ call popup_close(id)
+ bwipe!
+ call prop_type_delete('clipprop')
+endfunc
+
func Test_popup_clipwindow_top_clip()
CheckScreendump