From 2f39171a8df36f5ba8a86879e7d05f11af80209c Mon Sep 17 00:00:00 2001 From: Erik Reider <35975961+ErikReider@users.noreply.github.com> Date: Sat, 21 Sep 2024 14:35:35 +0200 Subject: [PATCH] Added fade-out animations for toplevels --- include/comp/object.h | 7 +++ include/desktop/toplevel.h | 1 + include/seat/seat.h | 4 ++ include/util.h | 14 +++++ src/comp/animation_mgr.c | 1 + src/comp/object.c | 23 ++++++++ src/comp/output.c | 28 +++++++--- src/comp/toplevel.c | 81 ++++++++++++++++++++-------- src/comp/widget.c | 6 ++- src/comp/workspace.c | 9 ++-- src/desktop/layer_shell.c | 7 ++- src/desktop/xdg.c | 2 +- src/desktop/xdg_popup.c | 6 ++- src/desktop/xwayland_toplevel.c | 3 +- src/desktop/xwayland_unmanaged.c | 3 +- src/util.c | 90 ++++++++++++++++++++++++++++++++ 16 files changed, 242 insertions(+), 43 deletions(-) diff --git a/include/comp/object.h b/include/comp/object.h index 0a47b86..9b69e58 100644 --- a/include/comp/object.h +++ b/include/comp/object.h @@ -20,6 +20,10 @@ enum comp_object_type { struct comp_object { // The root of the toplevel/layer_surface/widget struct wlr_scene_tree *scene_tree; + // Used to display the actual content + struct wlr_scene_tree *content_tree; + // Used for saved scene buffers + struct wlr_scene_tree *saved_tree; enum comp_object_type type; // The pointer to the ancestor which is type of `comp_object_type` @@ -31,4 +35,7 @@ struct comp_object *comp_object_at(struct comp_server *server, double lx, struct wlr_scene_buffer **scene_buffer, struct wlr_surface **surface); +void comp_object_save_buffer(struct comp_object *object); +void comp_object_remove_buffer(struct comp_object *object); + #endif // !FX_COMP_OBJECT_H diff --git a/include/desktop/toplevel.h b/include/desktop/toplevel.h index 3fd35a1..e2d03cd 100644 --- a/include/desktop/toplevel.h +++ b/include/desktop/toplevel.h @@ -95,6 +95,7 @@ struct comp_toplevel { } txn; bool destroying; + bool unmapped; struct comp_animation_client *animation; bool mapped; diff --git a/include/seat/seat.h b/include/seat/seat.h index a516374..854bda2 100644 --- a/include/seat/seat.h +++ b/include/seat/seat.h @@ -9,6 +9,10 @@ #include "desktop/layer_shell.h" #include "desktop/toplevel.h" +struct gesture_data { + float percent; +}; + struct comp_seat { struct comp_server *server; diff --git a/include/util.h b/include/util.h index c0f9db3..ba85975 100644 --- a/include/util.h +++ b/include/util.h @@ -3,6 +3,7 @@ #include #include +#include #include /* @@ -27,6 +28,19 @@ void scale_box(struct wlr_box *box, float scale); struct wlr_scene_tree *alloc_tree(struct wlr_scene_tree *parent); +/** + * Modified version of: + * https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4558 + * + * Create a new scene node which represents a snapshot of another node. + * + * The snapshot displays the same contents as the source node at the time of + * its creation. The snapshot is completely independent from the source node: + * when the source node is updated, the snapshot will stay as-is. + */ +struct wlr_scene_tree *wlr_scene_tree_snapshot(struct wlr_scene_node *node, + struct wlr_scene_tree *parent); + /** Get red component from HEX color */ double hex_red(const uint32_t *const col); /** Get green component from HEX color */ diff --git a/src/comp/animation_mgr.c b/src/comp/animation_mgr.c index 1303ebd..e060c11 100644 --- a/src/comp/animation_mgr.c +++ b/src/comp/animation_mgr.c @@ -54,6 +54,7 @@ void comp_animation_client_add(struct comp_animation_mgr *mgr, struct comp_animation_client *client) { comp_animation_client_remove(client); client->inited = true; + client->animating = true; client->progress = 0.0; wl_list_insert(&mgr->clients, &client->link); diff --git a/src/comp/object.c b/src/comp/object.c index 10195fd..4499bce 100644 --- a/src/comp/object.c +++ b/src/comp/object.c @@ -1,6 +1,8 @@ #include +#include #include "comp/object.h" +#include "util.h" struct comp_object *comp_object_at(struct comp_server *server, double lx, double ly, double *sx, double *sy, @@ -33,3 +35,24 @@ struct comp_object *comp_object_at(struct comp_server *server, double lx, } return tree->node.data; } + +void comp_object_save_buffer(struct comp_object *object) { + // Thanks Sway for the simple implementation! :) + if (object->saved_tree) { + wlr_log(WLR_INFO, "Trying to save already saved buffer..."); + comp_object_remove_buffer(object); + } + + wlr_scene_node_set_enabled(&object->content_tree->node, true); + object->saved_tree = wlr_scene_tree_snapshot(&object->content_tree->node, + object->scene_tree); + + wlr_scene_node_set_enabled(&object->content_tree->node, false); + wlr_scene_node_set_enabled(&object->saved_tree->node, true); +} + +void comp_object_remove_buffer(struct comp_object *object) { + wlr_scene_node_destroy(&object->saved_tree->node); + object->saved_tree = NULL; + wlr_scene_node_set_enabled(&object->content_tree->node, true); +} diff --git a/src/comp/output.c b/src/comp/output.c index fd6d6cd..c6b234a 100644 --- a/src/comp/output.c +++ b/src/comp/output.c @@ -131,6 +131,13 @@ static void output_frame(struct wl_listener *listener, void *data) { struct wlr_scene_output *scene_output = wlr_scene_get_scene_output(scene, output->wlr_output); + // output_configure_scene(output, &server.root_scene->tree.node, NULL); + // wlr_scene_optimized_blur_mark_dirty(scene); + // wlr_output_layout_get_box(server.output_layout, output->wlr_output, + // &output->geometry); + // wlr_scene_output_set_position(scene_output, output->geometry.x, + // output->geometry.y); + /* Render the scene if needed and commit the output */ wlr_scene_output_commit(scene_output, NULL); @@ -238,18 +245,19 @@ struct comp_output *comp_output_create(struct comp_server *server, output->server = server; output->object.scene_tree = alloc_tree(&server->root_scene->tree); + output->object.content_tree = alloc_tree(output->object.scene_tree); output->object.scene_tree->node.data = &output->object; output->object.data = output; output->object.type = COMP_OBJECT_TYPE_OUTPUT; // Initialize layers - output->layers.shell_background = alloc_tree(output->object.scene_tree); - output->layers.shell_bottom = alloc_tree(output->object.scene_tree); - output->layers.workspaces = alloc_tree(output->object.scene_tree); - output->layers.shell_top = alloc_tree(output->object.scene_tree); - output->layers.shell_overlay = alloc_tree(output->object.scene_tree); - output->layers.seat = alloc_tree(output->object.scene_tree); - output->layers.session_lock = alloc_tree(output->object.scene_tree); + output->layers.shell_background = alloc_tree(output->object.content_tree); + output->layers.shell_bottom = alloc_tree(output->object.content_tree); + output->layers.workspaces = alloc_tree(output->object.content_tree); + output->layers.shell_top = alloc_tree(output->object.content_tree); + output->layers.shell_overlay = alloc_tree(output->object.content_tree); + output->layers.seat = alloc_tree(output->object.content_tree); + output->layers.session_lock = alloc_tree(output->object.content_tree); wl_list_init(&output->workspaces); @@ -403,6 +411,12 @@ void comp_output_update_sizes(struct comp_output *output) { comp_output_arrange_layers(output); comp_output_arrange_output(output); + + // TODO: Update toplevel positions/tiling/sizes Only enable for actual + // outputs + // printf("GEO: %i %i %ix%i\n", output->geometry.width, + // output->geometry.height, output->geometry.x, + // output->geometry.y); } void comp_output_move_workspace_to(struct comp_output *dest_output, diff --git a/src/comp/toplevel.c b/src/comp/toplevel.c index 487eebf..2921392 100644 --- a/src/comp/toplevel.c +++ b/src/comp/toplevel.c @@ -444,25 +444,24 @@ struct wlr_scene_tree *comp_toplevel_get_layer(struct comp_toplevel *toplevel) { static void iter_scene_buffers_apply_effects(struct wlr_scene_buffer *buffer, int sx, int sy, void *user_data) { + struct comp_toplevel *toplevel = user_data; + struct wlr_scene_surface *scene_surface = wlr_scene_surface_try_from_buffer(buffer); - if (!scene_surface || !user_data) { - return; - } - - struct comp_toplevel *toplevel = user_data; - switch (toplevel->type) { - case COMP_TOPLEVEL_TYPE_XDG:; - struct wlr_xdg_surface *xdg_surface; - if (!(xdg_surface = wlr_xdg_surface_try_from_wlr_surface( - scene_surface->surface)) || - xdg_surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) { - return; + if (scene_surface) { + switch (toplevel->type) { + case COMP_TOPLEVEL_TYPE_XDG:; + struct wlr_xdg_surface *xdg_surface; + if (!(xdg_surface = wlr_xdg_surface_try_from_wlr_surface( + scene_surface->surface)) || + xdg_surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) { + return; + } + break; + case COMP_TOPLEVEL_TYPE_XWAYLAND: + // TODO: Check here + break; } - break; - case COMP_TOPLEVEL_TYPE_XWAYLAND: - // TODO: Check here - break; } bool has_effects = !toplevel->fullscreen; @@ -488,9 +487,14 @@ static void iter_scene_buffers_apply_effects(struct wlr_scene_buffer *buffer, } void comp_toplevel_mark_effects_dirty(struct comp_toplevel *toplevel) { + if (toplevel->object.saved_tree) { + wlr_scene_node_for_each_buffer(&toplevel->object.saved_tree->node, + iter_scene_buffers_apply_effects, + toplevel); + return; + } wlr_scene_node_for_each_buffer(&toplevel->toplevel_scene_tree->node, iter_scene_buffers_apply_effects, toplevel); - comp_toplevel_commit_transaction(toplevel, false); } void comp_toplevel_move_into_parent_tree(struct comp_toplevel *toplevel, @@ -893,6 +897,10 @@ void comp_toplevel_close(struct comp_toplevel *toplevel) { void comp_toplevel_destroy(struct comp_toplevel *toplevel) { toplevel->destroying = true; + if (toplevel->animation->animating) { + wlr_log(WLR_DEBUG, "Delaying destroy until animation finishes"); + return; + } comp_transaction_remove(&toplevel->txn.transaction); @@ -914,17 +922,32 @@ const struct comp_transaction_impl transaction_impl = { static void animation_update(struct comp_animation_mgr *mgr, struct comp_animation_client *client) { struct comp_toplevel *toplevel = client->data; - toplevel->opacity = client->progress; - toplevel->titlebar->widget.opacity = client->progress; + if (toplevel->unmapped) { + toplevel->opacity = 1 - client->progress; + toplevel->titlebar->widget.opacity = 1 - client->progress; + } else { + toplevel->opacity = client->progress; + toplevel->titlebar->widget.opacity = client->progress; + } comp_toplevel_mark_effects_dirty(toplevel); } static void animation_done(struct comp_animation_mgr *mgr, struct comp_animation_client *client) { struct comp_toplevel *toplevel = client->data; - toplevel->opacity = 1.0f; - toplevel->titlebar->widget.opacity = 1.0f; + if (toplevel->unmapped) { + toplevel->opacity = 0.0f; + toplevel->titlebar->widget.opacity = 0.0f; + } else { + toplevel->opacity = 1.0f; + toplevel->titlebar->widget.opacity = 1.0f; + } comp_toplevel_mark_effects_dirty(toplevel); + + // Continue destroying the toplevel + if (toplevel->destroying) { + comp_toplevel_destroy(toplevel); + } } const struct comp_animation_client_impl animation_impl = { @@ -963,12 +986,13 @@ comp_toplevel_init(struct comp_output *output, struct comp_workspace *workspace, toplevel->state.workspace = workspace; struct wlr_scene_tree *tree = comp_toplevel_get_layer(toplevel); toplevel->object.scene_tree = alloc_tree(tree); + toplevel->object.content_tree = alloc_tree(toplevel->object.scene_tree); toplevel->object.scene_tree->node.data = &toplevel->object; toplevel->object.data = toplevel; toplevel->object.type = COMP_OBJECT_TYPE_TOPLEVEL; - toplevel->decoration_scene_tree = alloc_tree(toplevel->object.scene_tree); + toplevel->decoration_scene_tree = alloc_tree(toplevel->object.content_tree); // Initialize saved position/size toplevel->saved_state.x = 0; @@ -1026,6 +1050,8 @@ static inline void set_natural_size(struct comp_toplevel *toplevel) { */ void comp_toplevel_generic_map(struct comp_toplevel *toplevel) { + toplevel->unmapped = false; + struct comp_workspace *ws = toplevel->state.workspace; comp_toplevel_set_pid(toplevel); @@ -1073,11 +1099,20 @@ void comp_toplevel_generic_map(struct comp_toplevel *toplevel) { } void comp_toplevel_generic_unmap(struct comp_toplevel *toplevel) { + toplevel->unmapped = true; + if (toplevel->fullscreen) { comp_toplevel_set_fullscreen(toplevel, false); } - wlr_scene_node_set_enabled(&toplevel->object.scene_tree->node, false); + // Don't animate if already destroying + if (!toplevel->destroying) { + comp_object_save_buffer(&toplevel->object); + toplevel->opacity = 1; + toplevel->titlebar->widget.opacity = 1; + comp_toplevel_mark_effects_dirty(toplevel); + comp_animation_client_add(server.animation_mgr, toplevel->animation); + } /* Reset the cursor mode if the grabbed toplevel was unmapped. */ if (toplevel == toplevel->server->seat->grabbed_toplevel) { diff --git a/src/comp/widget.c b/src/comp/widget.c index e8af461..7e4d87c 100644 --- a/src/comp/widget.c +++ b/src/comp/widget.c @@ -32,13 +32,15 @@ bool comp_widget_init(struct comp_widget *widget, struct comp_server *server, const struct comp_widget_impl *impl) { assert(parent_obj); widget->object.scene_tree = alloc_tree(parent_tree); - if (widget->object.scene_tree == NULL) { + widget->object.content_tree = alloc_tree(widget->object.scene_tree); + if (widget->object.scene_tree == NULL || + widget->object.content_tree == NULL) { wlr_log(WLR_ERROR, "Failed to allocate comp_titlebar wlr_scene_tree"); return false; } widget->scene_buffer = - wlr_scene_buffer_create(widget->object.scene_tree, NULL); + wlr_scene_buffer_create(widget->object.content_tree, NULL); if (widget->scene_buffer == NULL) { wlr_log(WLR_ERROR, "Failed to allocate comp_titlebar wlr_scene_buffer"); wlr_scene_node_destroy(&widget->object.scene_tree->node); diff --git a/src/comp/workspace.c b/src/comp/workspace.c index 43030b1..09244da 100644 --- a/src/comp/workspace.c +++ b/src/comp/workspace.c @@ -173,7 +173,8 @@ struct comp_workspace *comp_workspace_new(struct comp_output *output, // Create workspace tree ws->object.scene_tree = alloc_tree(output->layers.workspaces); - if (!ws->object.scene_tree) { + ws->object.content_tree = alloc_tree(ws->object.scene_tree); + if (!ws->object.scene_tree || !ws->object.content_tree) { return NULL; } ws->object.scene_tree->node.data = &ws->object; @@ -181,19 +182,19 @@ struct comp_workspace *comp_workspace_new(struct comp_output *output, ws->object.type = COMP_OBJECT_TYPE_WORKSPACE; // Create tiled/fullscreen - ws->layers.lower = alloc_tree(ws->object.scene_tree); + ws->layers.lower = alloc_tree(ws->object.content_tree); if (!ws->layers.lower) { return NULL; } ws->layers.lower->node.data = &ws->object; // Create floating - ws->layers.floating = alloc_tree(ws->object.scene_tree); + ws->layers.floating = alloc_tree(ws->object.content_tree); if (!ws->layers.floating) { return NULL; } ws->layers.floating->node.data = &ws->object; // Create unmanaged - ws->layers.unmanaged = alloc_tree(ws->object.scene_tree); + ws->layers.unmanaged = alloc_tree(ws->object.content_tree); if (!ws->layers.unmanaged) { return NULL; } diff --git a/src/desktop/layer_shell.c b/src/desktop/layer_shell.c index e10d24d..956c9e0 100644 --- a/src/desktop/layer_shell.c +++ b/src/desktop/layer_shell.c @@ -220,7 +220,10 @@ void layer_shell_new_surface(struct wl_listener *listener, void *data) { struct wlr_scene_tree *layer = layer_get_scene_tree(output, wlr_layer_surface->pending.layer); layer_surface->object.scene_tree = alloc_tree(layer); - if (layer_surface->object.scene_tree == NULL) { + layer_surface->object.content_tree = + alloc_tree(layer_surface->object.scene_tree); + if (layer_surface->object.scene_tree == NULL || + layer_surface->object.content_tree == NULL) { goto error; } @@ -229,7 +232,7 @@ void layer_shell_new_surface(struct wl_listener *listener, void *data) { */ layer_surface->scene_layer = wlr_scene_layer_surface_v1_create( - layer_surface->object.scene_tree, wlr_layer_surface); + layer_surface->object.content_tree, wlr_layer_surface); if (layer_surface->scene_layer == NULL) { wlr_log(WLR_ERROR, "Could not create wlr_scene_layer_surface"); goto error; diff --git a/src/desktop/xdg.c b/src/desktop/xdg.c index 1d544c8..12dec3d 100644 --- a/src/desktop/xdg.c +++ b/src/desktop/xdg.c @@ -378,7 +378,7 @@ void xdg_new_xdg_surface(struct wl_listener *listener, void *data) { // TODO: event.output_enter/output_leave for primary output toplevel->toplevel_scene_tree = wlr_scene_xdg_surface_create( - toplevel->object.scene_tree, toplevel_xdg->xdg_toplevel->base); + toplevel->object.content_tree, toplevel_xdg->xdg_toplevel->base); toplevel->toplevel_scene_tree->node.data = &toplevel->object; xdg_surface->data = toplevel->object.scene_tree; diff --git a/src/desktop/xdg_popup.c b/src/desktop/xdg_popup.c index 4c9865f..61895f8 100644 --- a/src/desktop/xdg_popup.c +++ b/src/desktop/xdg_popup.c @@ -142,11 +142,13 @@ struct comp_xdg_popup *xdg_new_xdg_popup(struct wlr_xdg_popup *wlr_popup, popup->parent_object = object; popup->wlr_popup = wlr_popup; popup->object.scene_tree = alloc_tree(parent); - if (popup->object.scene_tree == NULL) { + popup->object.content_tree = alloc_tree(popup->object.scene_tree); + if (popup->object.scene_tree == NULL || + popup->object.content_tree == NULL) { goto error_scene; } popup->xdg_scene_tree = - wlr_scene_xdg_surface_create(popup->object.scene_tree, wlr_popup->base); + wlr_scene_xdg_surface_create(popup->object.content_tree, wlr_popup->base); if (popup->xdg_scene_tree == NULL) { goto error_xdg; } diff --git a/src/desktop/xwayland_toplevel.c b/src/desktop/xwayland_toplevel.c index 67c84b5..b6f568f 100644 --- a/src/desktop/xwayland_toplevel.c +++ b/src/desktop/xwayland_toplevel.c @@ -405,7 +405,8 @@ static void xway_toplevel_map(struct wl_listener *listener, void *data) { // Insert the surface into the scene toplevel->toplevel_scene_tree = wlr_scene_subsurface_tree_create( - toplevel->object.scene_tree, toplevel_xway->xwayland_surface->surface); + toplevel->object.content_tree, + toplevel_xway->xwayland_surface->surface); toplevel->toplevel_scene_tree->node.data = &toplevel->object; if (toplevel->toplevel_scene_tree) { listener_connect(&toplevel->toplevel_scene_tree->node.events.destroy, diff --git a/src/desktop/xwayland_unmanaged.c b/src/desktop/xwayland_unmanaged.c index 6c98fb8..82ff17b 100644 --- a/src/desktop/xwayland_unmanaged.c +++ b/src/desktop/xwayland_unmanaged.c @@ -83,11 +83,12 @@ static void unmanaged_map(struct wl_listener *listener, void *data) { } unmanaged->object.scene_tree = alloc_tree(unmanaged->parent_tree); + unmanaged->object.content_tree = alloc_tree(unmanaged->object.scene_tree); unmanaged->object.scene_tree->node.data = &unmanaged->object; xsurface->data = unmanaged->object.scene_tree; unmanaged->surface_scene = wlr_scene_surface_create( - unmanaged->object.scene_tree, xsurface->surface); + unmanaged->object.content_tree, xsurface->surface); if (unmanaged->surface_scene) { unmanaged->surface_scene->buffer->node.data = &unmanaged->object; diff --git a/src/util.c b/src/util.c index 131408e..6828541 100644 --- a/src/util.c +++ b/src/util.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include "util.h" @@ -125,6 +126,95 @@ struct wlr_scene_tree *alloc_tree(struct wlr_scene_tree *parent) { return tree; } +static bool scene_node_snapshot(struct wlr_scene_node *node, int lx, int ly, + struct wlr_scene_tree *snapshot_tree) { + if (!node->enabled && node->type != WLR_SCENE_NODE_TREE) { + return true; + } + + lx += node->x; + ly += node->y; + + struct wlr_scene_node *snapshot_node = NULL; + switch (node->type) { + case WLR_SCENE_NODE_TREE:; + struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node); + + struct wlr_scene_node *child; + wl_list_for_each(child, &scene_tree->children, link) { + scene_node_snapshot(child, lx, ly, snapshot_tree); + } + break; + case WLR_SCENE_NODE_RECT:; + struct wlr_scene_rect *scene_rect = wlr_scene_rect_from_node(node); + + struct wlr_scene_rect *snapshot_rect = + wlr_scene_rect_create(snapshot_tree, scene_rect->width, + scene_rect->height, scene_rect->color); + if (snapshot_rect == NULL) { + return false; + } + snapshot_node = &snapshot_rect->node; + break; + case WLR_SCENE_NODE_BUFFER:; + struct wlr_scene_buffer *scene_buffer = + wlr_scene_buffer_from_node(node); + + struct wlr_scene_buffer *snapshot_buffer = + wlr_scene_buffer_create(snapshot_tree, NULL); + if (snapshot_buffer == NULL) { + return false; + } + snapshot_node = &snapshot_buffer->node; + + wlr_scene_buffer_set_dest_size(snapshot_buffer, scene_buffer->dst_width, + scene_buffer->dst_height); + wlr_scene_buffer_set_opaque_region(snapshot_buffer, + &scene_buffer->opaque_region); + wlr_scene_buffer_set_source_box(snapshot_buffer, + &scene_buffer->src_box); + wlr_scene_buffer_set_transform(snapshot_buffer, + scene_buffer->transform); + + struct wlr_scene_surface *scene_surface = + wlr_scene_surface_try_from_buffer(scene_buffer); + if (scene_surface != NULL && scene_surface->surface->buffer != NULL) { + wlr_scene_buffer_set_buffer(snapshot_buffer, + &scene_surface->surface->buffer->base); + } else { + wlr_scene_buffer_set_buffer(snapshot_buffer, scene_buffer->buffer); + } + break; + } + + if (snapshot_node != NULL) { + wlr_scene_node_set_position(snapshot_node, lx, ly); + } + + return true; +} + +struct wlr_scene_tree *wlr_scene_tree_snapshot(struct wlr_scene_node *node, + struct wlr_scene_tree *parent) { + struct wlr_scene_tree *snapshot = wlr_scene_tree_create(parent); + if (snapshot == NULL) { + return NULL; + } + + // Disable and enable the snapshot tree like so to atomically update + // the scene-graph. This will prevent over-damaging or other weirdness. + wlr_scene_node_set_enabled(&snapshot->node, false); + + if (!scene_node_snapshot(node, 0, 0, snapshot)) { + wlr_scene_node_destroy(&snapshot->node); + return NULL; + } + + wlr_scene_node_set_enabled(&snapshot->node, true); + + return snapshot; +} + double hex_red(const uint32_t *const col) { return ((const uint8_t *)(col))[3] / (double)(255); }