wayland: implement kaboom animation for dock icon removal

Splits icon into PIECE_SIZE tiles, animates them falling with
gravity using a wl_event_loop timer and a single scene buffer.
This commit is contained in:
lkn 2026-06-23 20:16:33 +02:00
parent 976bcce34c
commit 146f41008d
4 changed files with 1920 additions and 1 deletions

1708
src/Makefile Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,210 @@
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <wayland-server-core.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/interfaces/wlr_buffer.h>
#include <pixman.h>
#include <drm_fourcc.h>
#include "../../wconfig.h"
#include "../../screen.h"
#include "../../window.h"
#include "wl_types.h"
#ifdef NORMAL_ICON_KABOOM
#define PIECE_SIZE ICON_KABOOM_PIECE_SIZE
#define ANIM_FRAMES 12
#define ANIM_INTERVAL_MS 30
#define GRAVITY 2
struct kaboom_piece {
int x, y;
int vx, vy;
uint32_t pixels[PIECE_SIZE * PIECE_SIZE];
};
struct kaboom_state {
struct wlr_scene_tree *tree;
struct wlr_scene_buffer *scene_buf;
struct wlr_buffer base;
pixman_image_t *image;
struct wl_event_source *timer;
struct kaboom_piece *pieces;
int npieces;
int width, height;
int frame;
};
static void kaboom_destroy(struct kaboom_state *state)
{
if (state->timer)
wl_event_source_remove(state->timer);
if (state->tree)
wlr_scene_node_destroy(&state->tree->node);
if (state->image)
pixman_image_unref(state->image);
wlr_buffer_drop(&state->base);
free(state->pieces);
free(state);
}
static bool kaboom_buf_begin_data_ptr_access(struct wlr_buffer *buf,
uint32_t flags, void **data, uint32_t *format, size_t *stride)
{
struct kaboom_state *state = wl_container_of(buf, state, base);
(void)flags;
*data = pixman_image_get_data(state->image);
*format = DRM_FORMAT_ARGB8888;
*stride = pixman_image_get_stride(state->image);
return true;
}
static void kaboom_buf_end_data_ptr_access(struct wlr_buffer *buf)
{
(void)buf;
}
static void kaboom_buf_destroy(struct wlr_buffer *buf)
{
(void)buf;
}
static const struct wlr_buffer_impl kaboom_buf_impl = {
.destroy = kaboom_buf_destroy,
.begin_data_ptr_access = kaboom_buf_begin_data_ptr_access,
.end_data_ptr_access = kaboom_buf_end_data_ptr_access,
};
static void kaboom_render(struct kaboom_state *state)
{
/* Clear to transparent */
uint32_t *dst = pixman_image_get_data(state->image);
int stride = pixman_image_get_stride(state->image) / 4;
memset(dst, 0, stride * 4 * state->height);
/* Draw each piece at its current position */
for (int i = 0; i < state->npieces; i++) {
struct kaboom_piece *p = &state->pieces[i];
for (int py = 0; py < PIECE_SIZE; py++) {
int dy = p->y + py;
if (dy < 0 || dy >= state->height) continue;
for (int px = 0; px < PIECE_SIZE; px++) {
int dx = p->x + px;
if (dx < 0 || dx >= state->width) continue;
dst[dy * stride + dx] = p->pixels[py * PIECE_SIZE + px];
}
}
}
}
static int kaboom_timer_cb(void *data)
{
struct kaboom_state *state = data;
state->frame++;
if (state->frame >= ANIM_FRAMES) {
kaboom_destroy(state);
return 0;
}
/* Update piece positions */
for (int i = 0; i < state->npieces; i++) {
struct kaboom_piece *p = &state->pieces[i];
p->vy += GRAVITY;
p->x += p->vx;
p->y += p->vy;
}
kaboom_render(state);
wlr_scene_buffer_set_buffer_with_damage(state->scene_buf, &state->base, NULL);
wl_event_source_timer_update(state->timer, ANIM_INTERVAL_MS);
return 0;
}
void wl_do_kaboom(WScreen *scr, WNativeWindow win, int x, int y)
{
(void)scr;
struct wl_frame_buf *fb = frame_buf_find(win);
if (!fb || !fb->image || !wl_state.scene)
return;
int w = fb->width;
int h = fb->height;
int cols = w / PIECE_SIZE;
int rows = h / PIECE_SIZE;
int npieces = cols * rows;
if (npieces <= 0)
return;
struct kaboom_state *state = calloc(1, sizeof(*state));
if (!state) return;
state->pieces = calloc(npieces, sizeof(struct kaboom_piece));
if (!state->pieces) { free(state); return; }
state->npieces = npieces;
state->width = w;
state->height = h;
state->frame = 0;
/* Copy pixel data from icon into pieces */
uint32_t *src = pixman_image_get_data(fb->image);
int src_stride = pixman_image_get_stride(fb->image) / 4;
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
struct kaboom_piece *p = &state->pieces[r * cols + c];
p->x = c * PIECE_SIZE;
p->y = r * PIECE_SIZE;
/* Random velocity: horizontal spread, slight upward */
p->vx = (rand() % 7) - 3;
p->vy = -(rand() % 4) - 1;
for (int py = 0; py < PIECE_SIZE; py++) {
for (int px = 0; px < PIECE_SIZE; px++) {
int sx = c * PIECE_SIZE + px;
int sy = r * PIECE_SIZE + py;
if (sx < w && sy < h)
p->pixels[py * PIECE_SIZE + px] = src[sy * src_stride + sx];
}
}
}
}
/* Create scene buffer for animation */
state->image = pixman_image_create_bits(PIXMAN_a8r8g8b8, w, h, NULL, 0);
if (!state->image) { free(state->pieces); free(state); return; }
wlr_buffer_init(&state->base, &kaboom_buf_impl, w, h);
struct wlr_scene_tree *parent = wl_state.level_trees[WL_LAYER_OVERLAY]
? wl_state.level_trees[WL_LAYER_OVERLAY] : &wl_state.scene->tree;
state->tree = wlr_scene_tree_create(parent);
if (!state->tree) { kaboom_destroy(state); return; }
wlr_scene_node_set_position(&state->tree->node, x, y);
state->scene_buf = wlr_scene_buffer_create(state->tree, &state->base);
if (!state->scene_buf) { kaboom_destroy(state); return; }
/* Render first frame (original positions) and hide original icon */
kaboom_render(state);
wlr_scene_buffer_set_buffer_with_damage(state->scene_buf, &state->base, NULL);
if (fb->tree)
wlr_scene_node_set_enabled(&fb->tree->node, false);
else if (fb->scene_buf)
wlr_scene_node_set_enabled(&fb->scene_buf->node, false);
/* Start animation timer */
struct wl_event_loop *loop = wl_display_get_event_loop(wl_state.display);
state->timer = wl_event_loop_add_timer(loop, kaboom_timer_cb, state);
wl_event_source_timer_update(state->timer, ANIM_INTERVAL_MS);
}
#else /* !NORMAL_ICON_KABOOM */
void wl_do_kaboom(WScreen *scr, WNativeWindow win, int x, int y)
{
(void)scr; (void)win; (void)x; (void)y;
}
#endif

View File

@ -490,7 +490,7 @@ const WMBackend wl_backend = {
.clip_icon_paint = wl_clip_icon_paint,
.drawer_icon_paint = wl_drawer_icon_paint,
.balloon_init = wl_balloon_init,
.do_kaboom = wl_noop_do_kaboom,
.do_kaboom = wl_do_kaboom,
.menu_paint_entry = wl_menu_paint_entry,
.menu_draw_frame = wl_menu_draw_frame,
.draw_bevel = wl_noop_draw_bevel,

View File

@ -521,6 +521,7 @@ void fb_attach_tree_listener(struct wl_frame_buf *fb);
void fb_attach_scene_buf_listener(struct wl_frame_buf *fb);
void wl_frame_paint_button(WCoreWindow *button, WTexture *texture, unsigned long color, WPixmap *image, int pushed);
unsigned long wl_atom_intern(const char *name);
void wl_do_kaboom(WScreen *scr, WNativeWindow win, int x, int y);
/* Signalfd module declarations (extracted to wl_signalfd.c) */
int wl_signalfd_handler(int fd, uint32_t mask, void *data);