Phase C complete: balloon tooltips + window opacity

C1 (slide animations): already works - misc.c calls vtable window_move
C2 (menu scrolling): pure policy code, already works
C3 (balloon tooltips): pixman text render to wlr_scene_buffer overlay
C4 (cursor themes): wlr_xcursor_manager already in place
C5 (window opacity): read _NET_WM_WINDOW_OPACITY, apply to frame +
    child buffers + client surface via wlr_scene_buffer_set_opacity
This commit is contained in:
Window Maker 2026-05-23 14:45:15 +02:00
parent a0a27b56d5
commit 4c5f07f9ec
2 changed files with 170 additions and 14 deletions

View File

@ -98,6 +98,12 @@ Three major libraries:
- Button scene_bufs raised above titlebar (commit a34d828)
- Titlebar button rendering — proper colors, bevel, pressed state (commit 5a4d989)
- Button bevel colors derived from titlebar background (commit f3cb593)
- **Phase C complete** (2026-05-23):
- C1: Slide animations — already works (misc.c calls vtable window_move per frame)
- C2: Menu scrolling — pure policy code, already works
- C3: Balloon tooltips — pixman text render → wlr_scene_buffer overlay in WL_LAYER_OVERLAY
- C4: Cursor themes — wlr_xcursor_manager + wl_pointer_change_cursor already in place
- C5: Window opacity — reads _NET_WM_WINDOW_OPACITY from XWayland, applies wlr_scene_buffer_set_opacity
---
@ -136,11 +142,13 @@ Priority: HIGH — needed for daily-driver use.
| # | Task | Key Files | Complexity |
|---|------|-----------|------------|
| C1 | Slide animations (iconify/deiconify zoom) | vtable slide_window | Medium |
| C2 | Menu scrolling (long menus) | menu.c scroll logic | Low |
| C3 | Balloon tooltips (dock icon hover) | vtable balloon_show | Low |
| C4 | Cursor themes (xcursor loading) | wlr_xcursor_manager | Low |
| C5 | Window opacity (NETWM alpha) | vtable netwm_set_opacity | Low |
| C1 | ~~Slide animations (iconify/deiconify zoom)~~ | misc.c → vtable window_move | ✅ Done (already works) |
| C2 | ~~Menu scrolling (long menus)~~ | menu.c scroll logic | ✅ Done (pure policy) |
| C3 | ~~Balloon tooltips (dock icon hover)~~ | vtable balloon_show | ✅ Done |
| C4 | ~~Cursor themes (xcursor loading)~~ | wlr_xcursor_manager | ✅ Done (already works) |
| C5 | ~~Window opacity (NETWM alpha)~~ | vtable netwm_update_opacity | ✅ Done |
**Phase C: COMPLETE**
---
@ -254,11 +262,11 @@ NOW (daily-driver basics):
NEXT (visual completeness):
~~B1. App icon rendering (wrlib → pixman)~~ ✅
~~F1. wrlib RContext for Wayland~~ ✅
C1. Slide animations
C2. Menu scrolling
C3. Balloon tooltips
C4. Cursor themes
C5. Window opacity
~~C1. Slide animations~~ ✅
~~C2. Menu scrolling~~ ✅
~~C3. Balloon tooltips~~ ✅
~~C4. Cursor themes~~ ✅
~~C5. Window opacity~~ ✅
THEN (full feature parity):
D*. Test all WINGs widgets

View File

@ -7680,7 +7680,56 @@ static void wl_noop_netwm_init(WScreen *scr) { (void)scr; }
static void wl_noop_netwm_cleanup(WScreen *scr) { (void)scr; }
static void wl_noop_netwm_update_desktop(WScreen *scr) { (void)scr; }
static int wl_noop_netwm_get_current_desktop(WScreen *scr) { (void)scr; return -1; }
static void wl_noop_netwm_update_opacity(WWindow *wwin) { (void)wwin; }
static void wl_netwm_update_opacity(WWindow *wwin)
{
if (!wwin->frame)
return;
struct wl_frame_buf *fb = frame_buf_find(wwin->frame->core->window);
if (!fb || !fb->scene_buf)
return;
float alpha = 1.0f;
if (wl_state.x_display && wwin->client_win) {
Atom type;
int format;
unsigned long items, rest;
unsigned char *prop = NULL;
Atom opacity_atom = XInternAtom(wl_state.x_display,
"_NET_WM_WINDOW_OPACITY", False);
if (XGetWindowProperty(wl_state.x_display, (Window)wwin->client_win,
opacity_atom, 0L, 1L, False, AnyPropertyType,
&type, &format, &items, &rest, &prop) == Success
&& format == 32 && items == 1 && prop) {
unsigned long val = *(unsigned long *)prop;
alpha = (float)val / (float)0xFFFFFFFF;
}
if (prop)
XFree(prop);
}
wlr_scene_buffer_set_opacity(fb->scene_buf, alpha);
/* Apply to all child frame_bufs (titlebar buttons, resizebar) */
struct wl_frame_buf *child;
wl_list_for_each(child, &wl_state.frame_list, link) {
if (child->parent_id == fb->id && child->scene_buf)
wlr_scene_buffer_set_opacity(child->scene_buf, alpha);
}
/* Apply to the client surface */
struct wl_toplevel_view *v;
wl_list_for_each(v, &wl_state.toplevel_list, link) {
if (v->frame_id == fb->id && v->scene_node) {
struct wlr_scene_node *node;
wl_list_for_each(node, &v->scene_node->children, link) {
if (node->type == WLR_SCENE_NODE_BUFFER)
wlr_scene_buffer_set_opacity(
wlr_scene_buffer_from_node(node), alpha);
}
}
}
}
static void wl_noop_netwm_update_actions(WWindow *wwin, int del) { (void)wwin; (void)del; }
static void wl_noop_netwm_check_client_hints(WWindow *wwin, int *layer, int *workspace)
{ (void)wwin; (void)layer; (void)workspace; }
@ -8187,6 +8236,35 @@ static void wl_drawer_icon_paint(WAppIcon *aicon)
static void wl_balloon_init(WScreen *scr)
{
scr->balloon = wmalloc(sizeof(WBalloon));
memset(scr->balloon, 0, sizeof(WBalloon));
/* Create a frame_buf in the overlay layer for balloon tooltips */
if (wl_state.scene) {
struct wl_frame_buf *fb = wmalloc(sizeof(*fb));
memset(fb, 0, sizeof(*fb));
int w = 1, h = 1;
fb->image = pixman_image_create_bits(PIXMAN_a8r8g8b8, w, h, NULL, 0);
wlr_buffer_init(&fb->base, &frame_buf_impl, w, h);
fb->id = wl_state.next_synthetic_id++;
fb->width = w;
fb->height = h;
struct wlr_scene_tree *parent = wl_state.level_trees[WL_LAYER_OVERLAY]
? wl_state.level_trees[WL_LAYER_OVERLAY] : &wl_state.scene->tree;
fb->tree = wlr_scene_tree_create(parent);
if (fb->tree) {
wlr_scene_node_set_position(&fb->tree->node, 0, 0);
fb->scene_buf = wlr_scene_buffer_create(fb->tree, &fb->base);
if (fb->scene_buf)
wlr_scene_node_set_position(&fb->scene_buf->node, 0, 0);
wlr_scene_node_set_enabled(&fb->tree->node, false);
}
wl_list_insert(&wl_state.frame_list, &fb->link);
W_RegisterBacking(fb->id, fb->image);
scr->balloon->window = (Window)fb->id;
}
}
static void wl_noop_do_kaboom(WScreen *scr, WNativeWindow win, int x, int y)
{ (void)scr; (void)win; (void)x; (void)y; }
@ -8462,7 +8540,77 @@ static void wl_noop_icon_select_paint(WIcon *icon, int step)
{ (void)icon; (void)step; }
static unsigned long wl_noop_window_get_event_mask(WNativeWindow window)
{ (void)window; return 0; }
static void wl_noop_balloon_show(WScreen *scr, void *bal) { (void)scr; (void)bal; }
static void wl_balloon_show(WScreen *scr, void *bal_ptr)
{
WBalloon *bal = (WBalloon *)bal_ptr;
if (!bal->text || !bal->text[0])
return;
struct wl_frame_buf *fb = frame_buf_find((WNativeWindow)bal->window);
if (!fb)
return;
WMFont *font = scr->info_text_font;
int textlen = strlen(bal->text);
int tw = WMWidthOfString(font, bal->text, textlen);
int th = WMFontHeight(font);
int pad = 4;
int bw = tw + pad * 2;
int bh = th + pad * 2;
/* Resize pixman buffer if needed */
if (bw != fb->width || bh != fb->height) {
W_UnregisterBacking(fb->id);
pixman_image_unref(fb->image);
fb->image = pixman_image_create_bits(PIXMAN_a8r8g8b8, bw, bh, NULL, 0);
fb->width = bw;
fb->height = bh;
fb->base.width = bw;
fb->base.height = bh;
W_RegisterBacking(fb->id, fb->image);
}
/* Fill pale yellow background */
uint32_t *data = pixman_image_get_data(fb->image);
for (int i = 0; i < bw * bh; i++)
data[i] = 0xFFFFFFE0;
/* 1px black border */
for (int x = 0; x < bw; x++) {
data[x] = 0xFF000000;
data[(bh - 1) * bw + x] = 0xFF000000;
}
for (int y = 0; y < bh; y++) {
data[y * bw] = 0xFF000000;
data[y * bw + bw - 1] = 0xFF000000;
}
/* Draw text */
WMDrawString(scr->wmscreen, (WMDrawable)fb->id, scr->black, font,
pad, pad, bal->text, textlen);
/* Position near objectWindow */
int bx = 0, by = 0;
struct wl_frame_buf *obj = frame_buf_find((WNativeWindow)bal->objectWindow);
if (obj) {
bx = obj->x;
by = obj->y + bal->h + 2;
}
/* Clamp to screen */
if (bx + bw > scr->scr_width)
bx = scr->scr_width - bw;
if (by + bh > scr->scr_height)
by = by - bal->h - bh - 2;
if (fb->scene_buf) {
wlr_scene_node_set_position(&fb->scene_buf->node, bx, by);
wlr_scene_buffer_set_buffer(fb->scene_buf, &fb->base);
wlr_scene_node_set_enabled(&fb->tree->node, true);
}
bal->mapped = 1;
}
static void wl_noop_window_fill_stipple(WNativeWindow win, int x, int y, int w, int h)
{ (void)win; (void)x; (void)y; (void)w; (void)h; }
static int wl_noop_event_check_enter_leave(void *dock) { (void)dock; return 1; }
@ -8670,7 +8818,7 @@ const WMBackend wl_backend = {
.netwm_cleanup = wl_noop_netwm_cleanup,
.netwm_update_desktop = wl_noop_netwm_update_desktop,
.netwm_get_current_desktop = wl_noop_netwm_get_current_desktop,
.netwm_update_opacity = wl_noop_netwm_update_opacity,
.netwm_update_opacity = wl_netwm_update_opacity,
.netwm_update_actions = wl_noop_netwm_update_actions,
.netwm_check_client_hints = wl_noop_netwm_check_client_hints,
.netwm_check_initial_state = wl_noop_netwm_check_initial_state,
@ -8743,7 +8891,7 @@ const WMBackend wl_backend = {
.icon_paint_title_bg = wl_noop_icon_paint_title_bg,
.icon_select_paint = wl_noop_icon_select_paint,
.window_get_event_mask = wl_noop_window_get_event_mask,
.balloon_show = wl_noop_balloon_show,
.balloon_show = wl_balloon_show,
.window_fill_stipple = wl_noop_window_fill_stipple,
.event_check_enter_leave = wl_noop_event_check_enter_leave,
.event_allow_async = wl_noop_event_allow_async,