From 784fcfbfb26096bd9d68025abae823ce79261115 Mon Sep 17 00:00:00 2001 From: Levi Neely <141506390+lneely@users.noreply.github.com> Date: Wed, 13 May 2026 17:46:21 +0200 Subject: [PATCH] Implement frame button rendering (close/iconify icons) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add wl_frame_paint_button which renders button icons from XPM data into the parent frame buffer. Called both from the vtable slot and from wl_frame_paint during titlebar rendering. Uses PRED_CLOSE_XPM and PRED_ICONIFY_XPM parsed lazily via RGetImageFromXPMData. Also removes XEvent from all WFrameWindow callbacks (on_click_left, on_click_right, on_dblclick_right, on_mousedown_resizebar) — they now take WMEvent*. --- src/backend/wayland/wl_backend.c | 93 ++++++++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 3 deletions(-) diff --git a/src/backend/wayland/wl_backend.c b/src/backend/wayland/wl_backend.c index 33fd3fd..9d0c38e 100644 --- a/src/backend/wayland/wl_backend.c +++ b/src/backend/wayland/wl_backend.c @@ -203,6 +203,7 @@ #include /* ------------------------------------------------------------------ */ #include +#include "../../def_pixmaps.h" struct W_Screen; struct W_View; void W_WL_EnqueueEvent(struct W_Screen *scr, const WMEvent *ev, struct W_View *target); @@ -495,6 +496,10 @@ struct wl_compositor_state { * wl_event_loop_run() after the startup pump thread stops (to avoid racing * with the pump thread's frame-timer callback on the scene graph). */ WScreen *dock_pending_scr; + /* Button icon RImages (parsed from XPM on first use) */ + RImage *btn_close_img; + RImage *btn_iconify_img; + RImage *btn_kill_img; }; static struct wl_compositor_state wl_state; @@ -3681,6 +3686,9 @@ wl_frame_configure(WNativeWindow win, int x, int y, int width, int height) } } +static void wl_frame_paint_button(WCoreWindow *button, WTexture *texture, + unsigned long color, WPixmap *image, int pushed); + /* * wl_frame_paint — render the entire frame decoration into the pixman buffer. * @@ -3781,6 +3789,22 @@ wl_frame_paint(struct WFrameWindow *fwin) } } + /* --- Titlebar buttons --- */ + if (fwin->titlebar && fwin->top_width > 0) { + if (fwin->left_button && !fwin->flags.hide_left_button && + !fwin->flags.lbutton_dont_fit) + wl_frame_paint_button(fwin->left_button, + fwin->title_texture[state], + WMColorPixel(fwin->title_color[state]), + fwin->lbutton_image, False); + if (fwin->right_button && !fwin->flags.hide_right_button && + !fwin->flags.rbutton_dont_fit) + wl_frame_paint_button(fwin->right_button, + fwin->title_texture[state], + WMColorPixel(fwin->title_color[state]), + fwin->rbutton_image, False); + } + /* --- Resizebar --- */ if (fwin->resizebar && fwin->bottom_width > 0) { int rb_y = total_h - fwin->bottom_width; @@ -6209,9 +6233,72 @@ static void wl_noop_frame_alloc_border_pixel(unsigned long colormap, static void wl_noop_frame_update_texture(struct WFrameWindow *fwin) { (void)fwin; } -static void wl_noop_frame_paint_button(WCoreWindow *button, WTexture *texture, +static void wl_frame_paint_button(WCoreWindow *button, WTexture *texture, unsigned long color, WPixmap *image, int pushed) -{ (void)button; (void)texture; (void)color; (void)image; (void)pushed; } +{ + (void)texture; (void)color; (void)image; + WFrameWindow *fwin = (WFrameWindow *)button->descriptor.parent; + if (!fwin || !fwin->core) + return; + + struct wl_frame_buf *frame_fb = frame_buf_find(fwin->core->window); + struct wl_frame_buf *btn_fb = frame_buf_find(button->window); + if (!frame_fb || !frame_fb->image || !btn_fb) + return; + + int bx = btn_fb->rel_x; + int by = btn_fb->rel_y; + int bw = btn_fb->width; + int bh = btn_fb->height; + + /* Determine which icon to use */ + RImage *icon = NULL; + if (button == fwin->right_button) { + if (!wl_state.btn_close_img) + wl_state.btn_close_img = RGetImageFromXPMData( + button->screen_ptr->rcontext, PRED_CLOSE_XPM); + icon = wl_state.btn_close_img; + } else if (button == fwin->left_button) { + if (!wl_state.btn_iconify_img) + wl_state.btn_iconify_img = RGetImageFromXPMData( + button->screen_ptr->rcontext, PRED_ICONIFY_XPM); + icon = wl_state.btn_iconify_img; + } + if (!icon) + return; + + /* Blit icon centered in button area */ + uint32_t *dst = pixman_image_get_data(frame_fb->image); + int dst_stride = pixman_image_get_stride(frame_fb->image) / 4; + int ix = bx + (bw - icon->width) / 2 + (pushed ? 1 : 0); + int iy = by + (bh - icon->height) / 2 + (pushed ? 1 : 0); + + for (int y = 0; y < icon->height; y++) { + int dy = iy + y; + if (dy < 0 || dy >= frame_fb->height) continue; + uint32_t *dst_row = dst + dy * dst_stride; + for (int x = 0; x < icon->width; x++) { + int dx = ix + x; + if (dx < 0 || dx >= frame_fb->width) continue; + unsigned char r, g, b, a; + if (icon->format == RRGBAFormat) { + int off = (y * icon->width + x) * 4; + r = icon->data[off]; g = icon->data[off+1]; + b = icon->data[off+2]; a = icon->data[off+3]; + } else { + int off = (y * icon->width + x) * 3; + r = icon->data[off]; g = icon->data[off+1]; + b = icon->data[off+2]; a = 255; + } + if (a == 0) continue; /* transparent */ + dst_row[dx] = ((uint32_t)a << 24) | ((uint32_t)r << 16) | + ((uint32_t)g << 8) | (uint32_t)b; + } + } + + if (frame_fb->scene_buf) + wlr_scene_buffer_set_buffer(frame_fb->scene_buf, &frame_fb->base); +} static void wl_noop_netwm_init(WScreen *scr) { (void)scr; } static void wl_noop_netwm_cleanup(WScreen *scr) { (void)scr; } @@ -6978,7 +7065,7 @@ const WMBackend wl_backend = { .texture_free_gc = wl_texture_free_gc, .frame_alloc_border_pixel = wl_noop_frame_alloc_border_pixel, .frame_update_texture = wl_noop_frame_update_texture, - .frame_paint_button = wl_noop_frame_paint_button, + .frame_paint_button = wl_frame_paint_button, .netwm_init = wl_noop_netwm_init, .netwm_cleanup = wl_noop_netwm_cleanup, .netwm_update_desktop = wl_noop_netwm_update_desktop,