Metal: draw image relief and face box for image glyph strings

The IMAGE_GLYPH branch returned before any decoration: tab-bar and
tool-bar buttons (and :relief images) lost their relief, and boxed
image faces lost their box, leaving stale pixels in the box rows that
the inset background fill deliberately skips (seen as a white gap in
the tab-bar bottom edge under the new-tab button).

Port ns_draw_image_relief (button relief with tab/tool-bar margins),
draw the face box after the image like ns_draw_glyph_string, fill the
image background at full cell height (NS skips the box inset there),
honor the left box line and slice origin for the image position, and
factor the relief colors/edges into mtl_relief_colors/mtl_draw_relief.
Also give mouse-face runs box edges at their boundaries and stop
reading past first_glyph for image/composition strings in the box
code, both mirroring NS.
This commit is contained in:
Andros Fenollosa 2026-06-03 22:18:54 +02:00
parent 52e46fa7a8
commit b526ebab2c

View file

@ -2129,6 +2129,53 @@ mtl_shade_color (unsigned long c, double level, bool lighten)
return ((unsigned long) r << 16) | ((unsigned long) g << 8) | (unsigned long) b;
}
/* Compute the relief light/dark colors for glyph string S exactly like
ns_setup_relief_colors: NSColor highlight/shadowWithLevel on the face
background (or box color). These are appearance-dynamic (dark mode shifts
the highlight), so a plain blend toward pure white/black does not match
what NS renders (e.g. the mode-line top edge: NS ~177 vs blend ~211). */
static void
mtl_relief_colors (struct glyph_string *s, unsigned long *light,
unsigned long *dark)
{
struct face *face = s->face;
unsigned long base = face->use_box_color_for_shadows_p
? face->box_color : face->background;
if (s->hl == DRAW_CURSOR)
base = ns_color_to_pixel (FRAME_CURSOR_COLOR (s->f));
NSColor *bc = [NSColor colorWithUnsignedLong:base];
NSColor *lc = [bc highlightWithLevel:0.4];
NSColor *dc = [bc shadowWithLevel:0.4];
*light = lc ? ns_color_to_pixel (lc) : mtl_shade_color (base, 0.4, true);
*dark = dc ? ns_color_to_pixel (dc) : mtl_shade_color (base, 0.4, false);
}
/* Draw a relief inside rect R with simple rectangle edges (subset of
ns_draw_relief, good enough for the typical thin relief). Raised: light
top/left + dark bottom/right; sunken: the inverse. */
static void
mtl_draw_relief (MtlFrameData *fd, struct glyph_string *s, NSRect r,
int hth, int vth, bool raised_p,
bool top_p, bool bot_p, bool left_p, bool right_p)
{
unsigned long light, dark;
mtl_relief_colors (s, &light, &dark);
unsigned long tl = raised_p ? light : dark;
unsigned long br = raised_p ? dark : light;
if (top_p)
[fd fillRect:NSMakeRect (NSMinX (r), NSMinY (r), NSWidth (r), hth)
color:tl];
if (bot_p)
[fd fillRect:NSMakeRect (NSMinX (r), NSMaxY (r) - hth, NSWidth (r), hth)
color:br];
if (left_p)
[fd fillRect:NSMakeRect (NSMinX (r), NSMinY (r), vth, NSHeight (r))
color:tl];
if (right_p)
[fd fillRect:NSMakeRect (NSMaxX (r) - vth, NSMinY (r), vth, NSHeight (r))
color:br];
}
/* Draw the face box / relief around glyph string S (mode line, buttons, etc.).
Ports ns_dumpglyphs_box_or_relief + ns_draw_box/ns_draw_relief with simple
rectangle edges (good enough for the typical 1px relief). */
@ -2142,50 +2189,126 @@ mtl_draw_glyph_string_box (struct glyph_string *s, MtlFrameData *fd)
int vth = abs (face->box_vertical_line_width);
if (hth == 0 && vth == 0) return;
struct glyph *last_glyph = s->first_glyph + s->nchars - 1;
/* Image and composition strings have no usable nchars; their single glyph
carries the box flags (mirrors xterm's x_draw_glyph_string_box). */
struct glyph *last_glyph = (s->cmp || s->img)
? s->first_glyph
: s->first_glyph + s->nchars - 1;
int last_x = (s->row->full_width_p && !s->w->pseudo_window_p)
? WINDOW_RIGHT_EDGE_X (s->w)
: window_box_right (s->w, s->area);
int right_x = (s->row->full_width_p && s->extends_to_end_of_line_p
? last_x - 1
: min (last_x, s->x + s->background_width) - 1);
bool left_p = s->first_glyph->left_box_line_p;
bool right_p = last_glyph->right_box_line_p;
/* A mouse-face run gets box edges at its own boundaries, like NS. */
bool left_p = (s->first_glyph->left_box_line_p
|| (s->hl == DRAW_MOUSE_FACE
&& (s->prev == NULL || s->prev->hl != s->hl)));
bool right_p = (last_glyph->right_box_line_p
|| (s->hl == DRAW_MOUSE_FACE
&& (s->next == NULL || s->next->hl != s->hl)));
int x = s->x, y = s->y, w = right_x - s->x + 1, h = s->height;
if (w <= 0 || h <= 0) return;
unsigned long tl, br; /* top/left and bottom/right edge colors */
if (face->box == FACE_SIMPLE_BOX)
tl = br = face->box_color;
else
{
unsigned long base = face->use_box_color_for_shadows_p
? face->box_color : face->background;
if (s->hl == DRAW_CURSOR)
base = ns_color_to_pixel (FRAME_CURSOR_COLOR (s->f));
/* Use the same NSColor highlight/shadow used by ns_setup_relief_colors:
these are appearance-dynamic (dark mode shifts the highlight), so a
plain blend toward pure white/black does not match what NS renders
(e.g. the mode-line top edge: NS ~177 vs blend-to-white 211). */
NSColor *bc = [NSColor colorWithUnsignedLong:base];
NSColor *lc = [bc highlightWithLevel:0.4];
NSColor *dc = [bc shadowWithLevel:0.4];
unsigned long light = lc ? ns_color_to_pixel (lc)
: mtl_shade_color (base, 0.4, true);
unsigned long dark = dc ? ns_color_to_pixel (dc)
: mtl_shade_color (base, 0.4, false);
bool raised = (face->box == FACE_RAISED_BOX);
tl = raised ? light : dark;
br = raised ? dark : light;
unsigned long c = face->box_color;
[fd fillRect:NSMakeRect (x, y, w, hth) color:c]; /* top */
[fd fillRect:NSMakeRect (x, y + h - hth, w, hth) color:c]; /* bottom */
if (left_p)
[fd fillRect:NSMakeRect (x, y, vth, h) color:c]; /* left */
if (right_p)
[fd fillRect:NSMakeRect (x + w - vth, y, vth, h) color:c]; /* right */
}
else
mtl_draw_relief (fd, s, NSMakeRect (x, y, w, h), hth, vth,
face->box == FACE_RAISED_BOX,
true, true, left_p, right_p);
}
[fd fillRect:NSMakeRect (x, y, w, hth) color:tl]; /* top */
[fd fillRect:NSMakeRect (x, y + h - hth, w, hth) color:br]; /* bottom */
if (left_p)
[fd fillRect:NSMakeRect (x, y, vth, h) color:tl]; /* left */
if (right_p)
[fd fillRect:NSMakeRect (x + w - vth, y, vth, h) color:br]; /* right */
/* Draw the relief around image glyph string S (tab/tool bar buttons and
images with :relief). Port of ns_draw_image_relief. */
static void
mtl_draw_image_relief (struct glyph_string *s, MtlFrameData *fd)
{
int x1, y1, thick;
bool raised_p, top_p, bot_p, left_p, right_p;
int extra_x, extra_y;
int x = s->x;
int y = s->ybase - image_ascent (s->img, s->face, &s->slice);
if (s->face->box != FACE_NO_BOX
&& s->first_glyph->left_box_line_p
&& s->slice.x == 0)
x += max (s->face->box_vertical_line_width, 0);
if (s->slice.x == 0)
x += s->img->hmargin;
if (s->slice.y == 0)
y += s->img->vmargin;
if (s->hl == DRAW_IMAGE_SUNKEN || s->hl == DRAW_IMAGE_RAISED)
{
if (s->face->id == TAB_BAR_FACE_ID)
thick = (tab_bar_button_relief < 0
? DEFAULT_TAB_BAR_BUTTON_RELIEF
: min (tab_bar_button_relief, 1000000));
else
thick = (tool_bar_button_relief < 0
? DEFAULT_TOOL_BAR_BUTTON_RELIEF
: min (tool_bar_button_relief, 1000000));
raised_p = s->hl == DRAW_IMAGE_RAISED;
}
else
{
thick = eabs (s->img->relief);
raised_p = s->img->relief > 0;
}
x1 = x + s->slice.width - 1;
y1 = y + s->slice.height - 1;
extra_x = extra_y = 0;
if (s->face->id == TAB_BAR_FACE_ID)
{
if (CONSP (Vtab_bar_button_margin)
&& FIXNUMP (XCAR (Vtab_bar_button_margin))
&& FIXNUMP (XCDR (Vtab_bar_button_margin)))
{
extra_x = XFIXNUM (XCAR (Vtab_bar_button_margin)) - thick;
extra_y = XFIXNUM (XCDR (Vtab_bar_button_margin)) - thick;
}
else if (FIXNUMP (Vtab_bar_button_margin))
extra_x = extra_y = XFIXNUM (Vtab_bar_button_margin) - thick;
}
if (s->face->id == TOOL_BAR_FACE_ID)
{
if (CONSP (Vtool_bar_button_margin)
&& FIXNUMP (XCAR (Vtool_bar_button_margin))
&& FIXNUMP (XCDR (Vtool_bar_button_margin)))
{
extra_x = XFIXNUM (XCAR (Vtool_bar_button_margin));
extra_y = XFIXNUM (XCDR (Vtool_bar_button_margin));
}
else if (FIXNUMP (Vtool_bar_button_margin))
extra_x = extra_y = XFIXNUM (Vtool_bar_button_margin);
}
top_p = bot_p = left_p = right_p = false;
if (s->slice.x == 0)
x -= thick + extra_x, left_p = true;
if (s->slice.y == 0)
y -= thick + extra_y, top_p = true;
if (s->slice.x + s->slice.width == s->img->width)
x1 += thick + extra_x, right_p = true;
if (s->slice.y + s->slice.height == s->img->height)
y1 += thick + extra_y, bot_p = true;
if (thick > 0)
mtl_draw_relief (fd, s, NSMakeRect (x, y, x1 - x + 1, y1 - y + 1),
thick, thick, raised_p, top_p, bot_p, left_p, right_p);
}
/* Compute the underline offset below the baseline and its thickness, mirroring
@ -2375,7 +2498,13 @@ mtl_draw_glyph_string_impl (struct glyph_string *s)
box_line_width is 0, so this is identical to filling the full height. */
if (!s->background_filled_p)
{
int blw = face ? max (face->box_horizontal_line_width, 0) : 0;
/* Images get the full cell height: NS fills s->height without the box
inset there (ns_dumpglyphs_image notes the inset "was causing
problems w/tabbar mode"), and the face box is drawn after the
image anyway. */
int blw = (face && !(s->first_glyph
&& s->first_glyph->type == IMAGE_GLYPH))
? max (face->box_horizontal_line_width, 0) : 0;
NSRect bgr = NSMakeRect (s->x, s->y + blw,
s->background_width, s->height - 2 * blw);
[fd fillRect:bgr color:bg];
@ -2393,9 +2522,14 @@ mtl_draw_glyph_string_impl (struct glyph_string *s)
id<MTLTexture> tex = mtl_texture_for_image (img);
if (tex)
{
int x = s->x + s->img->hmargin;
int y = s->ybase - image_ascent (img, s->face, &s->slice)
+ s->img->vmargin;
int x = s->x;
int y = s->ybase - image_ascent (img, s->face, &s->slice);
/* Start to the right of a left box line, like NS. */
if (face && face->box != FACE_NO_BOX
&& s->first_glyph->left_box_line_p && s->slice.x == 0)
x += max (face->box_vertical_line_width, 0);
if (s->slice.x == 0) x += s->img->hmargin;
if (s->slice.y == 0) y += s->img->vmargin;
/* The texture holds the full display-size image; sample only
this glyph string's slice (insert-sliced-image). */
float tw = (float) tex.width, th = (float) tex.height;
@ -2409,7 +2543,15 @@ mtl_draw_glyph_string_impl (struct glyph_string *s)
(s->slice.y + s->slice.height) / th,
1.0f);
}
/* Relief around tab/tool bar buttons and :relief images. */
if (s->img->relief
|| s->hl == DRAW_IMAGE_RAISED || s->hl == DRAW_IMAGE_SUNKEN)
mtl_draw_image_relief (s, fd);
}
/* The face box is drawn after the image, like NS ("draw box if not
done already" at the end of ns_draw_glyph_string). */
if (face && face->box != FACE_NO_BOX)
mtl_draw_glyph_string_box (s, fd);
return;
}