diff --git a/assets/geometry-change.mp4 b/assets/geometry-change.mp4 new file mode 100644 index 0000000000..4ed18e73a2 Binary files /dev/null and b/assets/geometry-change.mp4 differ diff --git a/data/animation_presets.conf b/data/animation_presets.conf index f1ff06c80a..b2dc536f5a 100644 --- a/data/animation_presets.conf +++ b/data/animation_presets.conf @@ -162,4 +162,43 @@ fly-in = { (2, "direction", [-1, 1, 0, 0]), (3, "direction", [0, 0, 1, 1]), ); -} +}; +geometry-change = { + scale-x = { + curve = "cubic-bezier(0.07, 0.65, 0, 1)"; + duration = "placeholder0"; + start = "window-width-before / window-width"; + end = 1; + }; + scale-y = { + curve = "cubic-bezier(0.07, 0.65, 0, 1)"; + duration = "placeholder0"; + start = "window-height-before / window-height"; + end = 1; + }; + shadow-scale-x = "scale-x"; + shadow-scale-y = "scale-y"; + offset-x = { + curve = "cubic-bezier(0.07, 0.65, 0, 1)"; + duration = "placeholder0"; + start = "window-x-before - window-x"; + end = 0; + }; + offset-y = { + curve = "cubic-bezier(0.07, 0.65, 0, 1)"; + duration = "placeholder0"; + start = "window-y-before - window-y"; + end = 0; + }; + saved-image-blend = { + duration = "placeholder0"; + start = 1; + end = 0; + }; + shadow-offset-x = "offset-x"; + shadow-offset-y = "offset-y"; + *knobs = { + duration = 0.4; + }; + *placeholders = ((0, "duration")); +}; diff --git a/include/picom/backend.h b/include/picom/backend.h index e087894f1b..f8a60c9899 100644 --- a/include/picom/backend.h +++ b/include/picom/backend.h @@ -183,6 +183,7 @@ enum backend_command_op { /// will later be filled in by the renderer using this symbolic reference. enum backend_command_source { BACKEND_COMMAND_SOURCE_WINDOW, + BACKEND_COMMAND_SOURCE_WINDOW_SAVED, BACKEND_COMMAND_SOURCE_SHADOW, BACKEND_COMMAND_SOURCE_BACKGROUND, }; diff --git a/man/picom.1.adoc b/man/picom.1.adoc index 0dae6f9afd..3f3c0df857 100644 --- a/man/picom.1.adoc +++ b/man/picom.1.adoc @@ -515,6 +515,10 @@ animations = ({ _decrease-opacity_:: When the opacity of a window is decreased. + [[trigger-geometry]]_geometry_:: When the geometry of a window is changed. (EXPERIMENTAL) ++ +WARNING: The _geometry_ trigger is experimental. Using this means you accept the caveat that geometry animations will also trigger when you manually resize or move a window, like when you drag the window around with your mouse. + _suppressions_::: Which other animations should be suppressed when this animation is running. Normally, if another trigger is activated while an animation is already running, the animation in progress will be interrupted and the new animation will start. If you want to prevent this, you can set the `suppressions` option to a list of triggers that should be suppressed. This is optional, the default value for this is an empty list. @@ -594,6 +598,24 @@ endif::[] _duration_:: Duration of the animation in seconds. -- ++ +_geometry-change_::: ++ +Animate the geometry (i.e. size and position) change of the window. ++ +WARNING: This makes use of both the <> trigger, and the <> output variable. Both of these features are experimental and may not work as expected. ++ +-- +ifdef::env-web[] +video::assets/geometry-change.mp4[width=400] +endif::[] +-- ++ +-- +*Options*::: + + _duration_:: Duration of the animation in seconds. +-- === Advanced @@ -693,6 +715,10 @@ Currently, these output variables are supported: ::: _crop-x_, _crop-y_, _crop-width_, _crop-height_:: These four values combined defines a rectangle on the screen. The window and its shadow will be cropped to this rectangle. If not defined, the window and shadow will not be cropped. + [[saved-image-blend]]_saved-image-blend_:: When the window's geometry changes, its content will often change drastically, creating a jarring discontinuity. This output variable allows you to blend the window's content before and after the geometry change, the before and after images will be stretched appropriately to match the animation. This way you can smoothly animated geometry changes. This is a number between 0 and 1. 0 means the saved image is not used, whereas 1 means you will only see the saved image. (EXPERIMENTAL) ++ +WARNING: The _saved-image-blend_ variable is experimental. It might work incorrectly, cause visual artifacts, or slow down your system. You are welcome to open an issue on GitHub if you encounter any problems to help us improve it, though resolution is not guaranteed. + All coordinates are in pixels, and are in the coordinate system of the screen. Sizes are also in pixels. IMPORTANT: If an output variable name is not defined in your animation script, it will take the default value for whichever state the window is in. Specifically, if you don't define an _opacity_ variable in the animation script for the "close" or "hide" trigger, a closed window will, by default, have 0 opacity. So you will just see it disappear instantly. Oftentimes, you will want to set _opacity_ to 1 to make the window visible for the duration of the animation. @@ -713,6 +739,8 @@ Currently, these context variables are defined: ::: _window-width_, _window-height_:: The size of the window. + _window-x-before_, _window-y-before_, _window-width-before_, _window-height-before_:: The size and coordinates of the window before the animation is triggered. This is only meaningfully different from the normal window geometry variables for the _geometry_ trigger. + _window-monitor-x_, _window-monitor-y_, _window-monitor-width_, _window-monitor-height_:: Defines the rectangle which reflects the monitor the window is on. If the window is not fully contained in any monitor, the rectangle will reflect the entire virtual screen. _window-raw-opacity-before_, _window-raw-opacity_:: Animation triggers are usually accompanied by a change in the window's opacity. For example, when a window is opened, its opacity changes from 0 to 1. These two variables reflect the opacity of the window before and after the animation is triggered. They are useful if you want to smoothly transition the window's opacity. diff --git a/src/backend/backend.c b/src/backend/backend.c index 5dcc19548e..6eae7386d8 100644 --- a/src/backend/backend.c +++ b/src/backend/backend.c @@ -11,6 +11,7 @@ #include "config.h" #include "log.h" #include "region.h" +#include "renderer/layout.h" #include "wm/win.h" #include "x.h" @@ -93,6 +94,9 @@ bool backend_execute(struct backend_base *backend, image_handle target, unsigned if (!pixman_region32_not_empty(cmd->blit.target_mask)) { continue; } + if (cmd->blit.opacity < 1. / MAX_ALPHA) { + continue; + } succeeded = backend->ops.blit(backend, cmd->origin, target, &cmd->blit); break; @@ -121,6 +125,7 @@ bool backend_execute(struct backend_base *backend, image_handle target, unsigned static inline const char *render_command_source_name(enum backend_command_source source) { switch (source) { case BACKEND_COMMAND_SOURCE_WINDOW: return "window"; + case BACKEND_COMMAND_SOURCE_WINDOW_SAVED: return "window_saved"; case BACKEND_COMMAND_SOURCE_SHADOW: return "shadow"; case BACKEND_COMMAND_SOURCE_BACKGROUND: return "background"; } diff --git a/src/config.h b/src/config.h index 786c9fed57..73b68ec30c 100644 --- a/src/config.h +++ b/src/config.h @@ -70,7 +70,6 @@ enum vblank_scheduler_type { }; enum animation_trigger { - ANIMATION_TRIGGER_INVALID = -1, /// When a hidden window is shown ANIMATION_TRIGGER_SHOW = 0, /// When a window is hidden @@ -83,7 +82,11 @@ enum animation_trigger { ANIMATION_TRIGGER_OPEN, /// When a window is closed ANIMATION_TRIGGER_CLOSE, - ANIMATION_TRIGGER_LAST = ANIMATION_TRIGGER_CLOSE, + /// When a window's geometry changes + ANIMATION_TRIGGER_GEOMETRY, + + ANIMATION_TRIGGER_INVALID, + ANIMATION_TRIGGER_COUNT = ANIMATION_TRIGGER_INVALID, }; static const char *animation_trigger_names[] attr_unused = { @@ -93,6 +96,7 @@ static const char *animation_trigger_names[] attr_unused = { [ANIMATION_TRIGGER_DECREASE_OPACITY] = "decrease-opacity", [ANIMATION_TRIGGER_OPEN] = "open", [ANIMATION_TRIGGER_CLOSE] = "close", + [ANIMATION_TRIGGER_GEOMETRY] = "geometry", }; struct script; @@ -192,7 +196,7 @@ struct window_maybe_options { enum tristate full_shadow; /// Window specific animations - struct win_script animations[ANIMATION_TRIGGER_LAST + 1]; + struct win_script animations[ANIMATION_TRIGGER_COUNT]; }; // Make sure `window_options` has no implicit padding. @@ -214,7 +218,7 @@ struct window_options { bool paint; bool full_shadow; - struct win_script animations[ANIMATION_TRIGGER_LAST + 1]; + struct win_script animations[ANIMATION_TRIGGER_COUNT]; }; #pragma GCC diagnostic pop @@ -432,7 +436,7 @@ typedef struct options { bool dithered_present; // === Animation === - struct win_script animations[ANIMATION_TRIGGER_LAST + 1]; + struct win_script animations[ANIMATION_TRIGGER_COUNT]; /// Array of all the scripts used in `animations`. This is a dynarr. struct script **all_scripts; @@ -521,5 +525,6 @@ static inline void log_warn_both_style_of_rules(const char *option_name) { "precedence, and \"%s\" will have no effect.", option_name, option_name); } +enum animation_trigger parse_animation_trigger(const char *trigger); // vim: set noet sw=8 ts=8 : diff --git a/src/config_libconfig.c b/src/config_libconfig.c index aff1b38b5b..a90a00a0c2 100644 --- a/src/config_libconfig.c +++ b/src/config_libconfig.c @@ -224,8 +224,8 @@ static inline void parse_wintype_config(const config_t *cfg, const char *member_ } } -static enum animation_trigger parse_animation_trigger(const char *trigger) { - for (int i = 0; i <= ANIMATION_TRIGGER_LAST; i++) { +enum animation_trigger parse_animation_trigger(const char *trigger) { + for (unsigned i = 0; i < ANIMATION_TRIGGER_COUNT; i++) { if (strcasecmp(trigger, animation_trigger_names[i]) == 0) { return i; } @@ -297,7 +297,7 @@ static bool parse_animation_one(struct win_script *animations, } auto number_of_triggers = config_setting_get_string(triggers) == NULL ? config_setting_length(triggers) : 1; - if (number_of_triggers > ANIMATION_TRIGGER_LAST) { + if (number_of_triggers > ANIMATION_TRIGGER_COUNT) { log_error("Too many triggers in animation defined at line %d", config_setting_source_line(triggers)); return false; diff --git a/src/dbus.c b/src/dbus.c index e291f829da..e0770d4671 100644 --- a/src/dbus.c +++ b/src/dbus.c @@ -555,9 +555,7 @@ cdbus_process_list_win(session_t *ps, DBusMessage *msg attr_unused, DBusMessage return DBUS_HANDLER_RESULT_HANDLED; } -/** - * Process a win_get D-Bus request. - */ +/// Process a property Get D-Bus request. static DBusHandlerResult cdbus_process_window_property_get(session_t *ps, DBusMessage *msg, cdbus_window_t wid, DBusMessage *reply, DBusError *e) { @@ -1125,9 +1123,7 @@ static DBusHandlerResult cdbus_process_introspect(DBusMessage *reply) { } ///@} -/** - * Process an D-Bus Introspect request, for /windows. - */ +/// Process an D-Bus Introspect request, for /windows. static DBusHandlerResult cdbus_process_windows_root_introspect(session_t *ps, DBusMessage *reply) { static const char *str_introspect = @@ -1209,6 +1205,11 @@ static bool cdbus_process_window_introspect(DBusMessage *reply) { " \n" " \n" " \n" + " \n" + " \n" + " \n" + " \n" + " \n" " \n" "\n"; // clang-format on @@ -1424,6 +1425,39 @@ cdbus_process_windows(DBusConnection *conn, DBusMessage *msg, void *ud) { "Unexpected member \"%s\" of dbus properties interface.", member); dbus_set_error_const(&err, DBUS_ERROR_UNKNOWN_METHOD, NULL); } + } else if (strcmp(interface, PICOM_WINDOW_INTERFACE) == 0 && + strcmp(member, "BlockUnblockAnimation") == 0) { + bool block = false; + const char *trigger_str = NULL; + if (!cdbus_msg_get_arg(msg, 0, DBUS_TYPE_STRING, &trigger_str) || + !cdbus_msg_get_arg(msg, 1, DBUS_TYPE_BOOLEAN, &block)) { + dbus_set_error_const(&err, DBUS_ERROR_INVALID_ARGS, NULL); + goto finished; + } + auto trigger = parse_animation_trigger(trigger_str); + if (trigger == ANIMATION_TRIGGER_INVALID) { + dbus_set_error(&err, CDBUS_ERROR_BADTGT, CDBUS_ERROR_BADTGT_S, + trigger_str); + goto finished; + } + auto cursor = wm_find(ps->wm, wid); + if (cursor == NULL) { + dbus_set_error(&err, CDBUS_ERROR_BADWIN, CDBUS_ERROR_BADWIN_S, wid); + goto finished; + } + auto w = wm_ref_deref(cursor); + unsigned count = 0; + if (w != NULL) { + if (block) { + w->animation_block[trigger] += 1; + } else if (w->animation_block[trigger] > 0) { + w->animation_block[trigger] -= 1; + } + count = w->animation_block[trigger]; + } + if (reply != NULL && !cdbus_append_uint32(reply, count)) { + ret = DBUS_HANDLER_RESULT_NEED_MEMORY; + } } else { log_debug("Illegal message of type \"%s\", path \"%s\" " "interface \"%s\", member \"%s\"", diff --git a/src/inspect.c b/src/inspect.c index 7da0f8f6b6..097ba77c08 100644 --- a/src/inspect.c +++ b/src/inspect.c @@ -218,7 +218,7 @@ void inspect_dump_window_maybe_options(struct window_maybe_options wopts) { } char **animation_triggers = dynarr_new(char *, 0); - for (int i = 0; i <= ANIMATION_TRIGGER_LAST; i++) { + for (int i = 0; i < ANIMATION_TRIGGER_COUNT; i++) { if (wopts.animations[i].script != NULL) { char *name = NULL; casprintf(&name, "\"%s\"", animation_trigger_names[i]); diff --git a/src/picom.c b/src/picom.c index ca10d8c8b5..ddf4973a6f 100644 --- a/src/picom.c +++ b/src/picom.c @@ -1649,13 +1649,9 @@ static void handle_pending_updates(struct session *ps, double delta_t) { // Process window flags. This needs to happen before `refresh_images` // because this might set the pixmap stale window flag. refresh_windows(ps); + ps->pending_updates = false; } - // Process window flags (stale images) - refresh_images(ps); - - ps->pending_updates = false; - wm_stack_foreach_safe(ps->wm, cursor, tmp) { auto w = wm_ref_deref(cursor); BUG_ON(w != NULL && w->tree_ref != cursor); @@ -1665,6 +1661,11 @@ static void handle_pending_updates(struct session *ps, double delta_t) { free(w->running_animation_instance); w->running_animation_instance = NULL; w->in_openclose = false; + if (w->saved_win_image != NULL) { + ps->backend_data->ops.release_image(ps->backend_data, + w->saved_win_image); + w->saved_win_image = NULL; + } if (w->state == WSTATE_UNMAPPED) { unmap_win_finish(ps, w); } else if (w->state == WSTATE_DESTROYED) { @@ -1672,6 +1673,10 @@ static void handle_pending_updates(struct session *ps, double delta_t) { } } } + + // Process window flags (stale images) + refresh_images(ps); + assert(ps->pending_updates == false); } /** diff --git a/src/renderer/command_builder.c b/src/renderer/command_builder.c index a8c791ee6e..ac65aba5cc 100644 --- a/src/renderer/command_builder.c +++ b/src/renderer/command_builder.c @@ -15,10 +15,11 @@ /// it's stored in `cmd` going backwards, i.e. cmd - 1, -2, ... /// @return number of commands generated static inline unsigned -commands_for_window_body(struct layer *layer, struct backend_command *cmd, +commands_for_window_body(struct layer *layer, struct backend_command *cmd_base, const region_t *frame_region, bool inactive_dim_fixed, double max_brightness) { auto w = layer->win; + auto cmd = cmd_base; scoped_region_t crop = region_from_box(layer->crop); auto mode = win_calc_mode_raw(layer->win); int border_width = w->g.border_width; @@ -48,18 +49,20 @@ commands_for_window_body(struct layer *layer, struct backend_command *cmd, if (layer->options.corner_radius > 0) { win_region_remove_corners(w, layer->window.origin, &cmd->opaque_region); } - region_scale(&cmd->target_mask, layer->window.origin, layer->scale); - region_scale(&cmd->opaque_region, layer->window.origin, layer->scale); - pixman_region32_intersect(&cmd->target_mask, &cmd->target_mask, &crop); - pixman_region32_intersect(&cmd->opaque_region, &cmd->opaque_region, &crop); - cmd->op = BACKEND_COMMAND_BLIT; - cmd->source = BACKEND_COMMAND_SOURCE_WINDOW; - cmd->origin = layer->window.origin; - cmd->blit = (struct backend_blit_args){ + + float opacity = layer->opacity * (1 - layer->saved_image_blend); + if (opacity > (1. - 1. / MAX_ALPHA)) { + // Avoid division by a very small number + opacity = 1; + } + float opacity_saved = 0; + if (opacity < 1) { + opacity_saved = layer->opacity * layer->saved_image_blend / (1 - opacity); + } + struct backend_blit_args args_base = { .border_width = border_width, - .target_mask = &cmd->target_mask, .corner_radius = layer->options.corner_radius, - .opacity = layer->opacity, + .opacity = opacity, .dim = dim, .scale = layer->scale, .effective_size = layer->window.size, @@ -68,11 +71,37 @@ commands_for_window_body(struct layer *layer, struct backend_command *cmd, .source_mask = NULL, .max_brightness = max_brightness, }; + region_scale(&cmd->target_mask, layer->window.origin, layer->scale); + region_scale(&cmd->opaque_region, layer->window.origin, layer->scale); + pixman_region32_intersect(&cmd->target_mask, &cmd->target_mask, &crop); + pixman_region32_intersect(&cmd->opaque_region, &cmd->opaque_region, &crop); + cmd->op = BACKEND_COMMAND_BLIT; + cmd->source = BACKEND_COMMAND_SOURCE_WINDOW; + cmd->origin = layer->window.origin; + cmd->blit = args_base; + cmd->blit.target_mask = &cmd->target_mask; + cmd -= 1; + if (layer->saved_image_blend > 0) { + pixman_region32_copy(&cmd->target_mask, &cmd[1].target_mask); + cmd->opaque_region = cmd[1].opaque_region; + pixman_region32_init(&cmd[1].opaque_region); + cmd->op = BACKEND_COMMAND_BLIT; + cmd->source = BACKEND_COMMAND_SOURCE_WINDOW_SAVED; + cmd->origin = layer->window.origin; + cmd->blit = args_base; + cmd->blit.effective_size = (ivec2){ + .width = (int)(layer->window.size.width / w->saved_win_image_scale.width), + .height = (int)(layer->window.size.height / w->saved_win_image_scale.height), + }; + cmd->blit.opacity = opacity_saved; + cmd->blit.target_mask = &cmd->target_mask; + cmd->blit.scale = vec2_scale(cmd->blit.scale, w->saved_win_image_scale); + cmd -= 1; + } if (w->frame_opacity == 1 || w->frame_opacity == 0) { - return 1; + return (unsigned)(cmd_base - cmd); } - cmd -= 1; pixman_region32_copy(&cmd->target_mask, frame_region); region_scale(&cmd->target_mask, cmd->origin, layer->scale); @@ -81,10 +110,27 @@ commands_for_window_body(struct layer *layer, struct backend_command *cmd, cmd->op = BACKEND_COMMAND_BLIT; cmd->origin = layer->window.origin; cmd->source = BACKEND_COMMAND_SOURCE_WINDOW; - cmd->blit = cmd[1].blit; + cmd->blit = args_base; cmd->blit.target_mask = &cmd->target_mask; - cmd->blit.opacity *= w->frame_opacity; - return 2; + cmd->blit.opacity = w->frame_opacity * opacity; + cmd -= 1; + if (layer->saved_image_blend > 0) { + pixman_region32_copy(&cmd->target_mask, &cmd[1].target_mask); + pixman_region32_init(&cmd->opaque_region); + cmd->op = BACKEND_COMMAND_BLIT; + cmd->source = BACKEND_COMMAND_SOURCE_WINDOW_SAVED; + cmd->origin = layer->window.origin; + cmd->blit = args_base; + cmd->blit.effective_size = (ivec2){ + .width = (int)(layer->window.size.width / w->saved_win_image_scale.width), + .height = (int)(layer->window.size.height / w->saved_win_image_scale.height), + }; + cmd->blit.opacity = w->frame_opacity * opacity_saved; + cmd->blit.target_mask = &cmd->target_mask; + cmd->blit.scale = vec2_scale(cmd->blit.scale, w->saved_win_image_scale); + cmd -= 1; + } + return (unsigned)(cmd_base - cmd); } /// Generate render command for the shadow in `layer` @@ -115,7 +161,8 @@ command_for_shadow(struct layer *layer, struct backend_command *cmd, // should be blits for the current window. for (auto j = cmd + 1; j != end; j++) { assert(j->op == BACKEND_COMMAND_BLIT); - assert(j->source == BACKEND_COMMAND_SOURCE_WINDOW); + assert(j->source == BACKEND_COMMAND_SOURCE_WINDOW || + j->source == BACKEND_COMMAND_SOURCE_WINDOW_SAVED); if (j->blit.corner_radius == 0) { pixman_region32_subtract( &cmd->target_mask, &cmd->target_mask, &j->target_mask); @@ -375,11 +422,16 @@ void command_builder_build(struct command_builder *cb, struct layout *layout, if (layer->options.shadow) { ncmds += 1; } + + unsigned n_cmds_for_window_body = 1; if (layer->win->frame_opacity < 1 && layer->win->frame_opacity > 0) { // Needs to draw the frame separately - ncmds += 1; + n_cmds_for_window_body += 1; + } + if (layer->saved_image_blend > 0) { + n_cmds_for_window_body *= 2; } - ncmds += 1; // window body + ncmds += n_cmds_for_window_body; // window body } auto list = command_builder_command_list_new(cb, ncmds); diff --git a/src/renderer/damage.c b/src/renderer/damage.c index 48c650d5fd..5778e717be 100644 --- a/src/renderer/damage.c +++ b/src/renderer/damage.c @@ -30,6 +30,10 @@ layer_compare(const struct layer *past_layer, const struct backend_command *past // Shadow moved or size changed return false; } + if (past_layer->saved_image_blend != curr_layer->saved_image_blend) { + // The amount of blending with the saved image changed + return false; + } if (past_layer->number_of_commands != curr_layer->number_of_commands) { // Number of render commands changed. We are being conservative // here, because even though the number of commands changed, we can still diff --git a/src/renderer/layout.c b/src/renderer/layout.c index f7f8401105..6c828bba47 100644 --- a/src/renderer/layout.c +++ b/src/renderer/layout.c @@ -101,6 +101,12 @@ static bool layer_from_window(struct layer *out_layer, struct win *w, ivec2 size goto out; } + out_layer->saved_image_blend = + (float)win_animatable_get(w, WIN_SCRIPT_SAVED_IMAGE_BLEND); + if (w->saved_win_image == NULL) { + out_layer->saved_image_blend = 0; + } + pixman_region32_copy(&out_layer->damaged, &w->damaged); pixman_region32_translate(&out_layer->damaged, out_layer->window.origin.x, out_layer->window.origin.y); diff --git a/src/renderer/layout.h b/src/renderer/layout.h index f370de9749..66b64790c6 100644 --- a/src/renderer/layout.h +++ b/src/renderer/layout.h @@ -38,6 +38,8 @@ struct layer { float blur_opacity; /// Opacity of this window's shadow float shadow_opacity; + /// How much the image of this window should be blended with the saved image + float saved_image_blend; /// Crop the content of this layer to this box, in screen coordinates. struct ibox crop; diff --git a/src/renderer/renderer.c b/src/renderer/renderer.c index aa86b5bc75..d228bcd1f1 100644 --- a/src/renderer/renderer.c +++ b/src/renderer/renderer.c @@ -416,6 +416,9 @@ static bool renderer_prepare_commands(struct renderer *r, struct backend_base *b } else if (cmd->source == BACKEND_COMMAND_SOURCE_WINDOW) { assert(w->win_image); cmd->blit.source_image = w->win_image; + } else if (cmd->source == BACKEND_COMMAND_SOURCE_WINDOW_SAVED) { + assert(w->saved_win_image); + cmd->blit.source_image = w->saved_win_image; } if (cmd->blit.source_mask != NULL) { if (w->mask_image == NULL && diff --git a/src/transition/generated/script_templates.c b/src/transition/generated/script_templates.c index 25aff2c1ec..8be7063774 100644 --- a/src/transition/generated/script_templates.c +++ b/src/transition/generated/script_templates.c @@ -81,11 +81,11 @@ static struct script *script_template__disappear(int *output_slots) { {.type = INST_STORE, .slot = 10}, {.type = INST_BRANCH_ONCE, .rel = 15}, {.type = INST_HALT}, - {.type = INST_LOAD_CTX, .ctx = 32}, + {.type = INST_LOAD_CTX, .ctx = 64}, {.type = INST_STORE_OVER_NAN, .slot = 13}, {.type = INST_LOAD_CTX, .ctx = 1073741824}, {.type = INST_STORE, .slot = 15}, - {.type = INST_LOAD_CTX, .ctx = 40}, + {.type = INST_LOAD_CTX, .ctx = 72}, {.type = INST_STORE, .slot = 14}, {.type = INST_IMM, .imm = 0x1p+0}, {.type = INST_STORE_OVER_NAN, .slot = 16}, @@ -209,6 +209,7 @@ static struct script *script_template__disappear(int *output_slots) { output_slots[12] = -1; output_slots[13] = -1; output_slots[14] = -1; + output_slots[15] = -1; return ret; } @@ -299,11 +300,11 @@ static struct script *script_template__appear(int *output_slots) { {.type = INST_STORE, .slot = 10}, {.type = INST_BRANCH_ONCE, .rel = 13}, {.type = INST_HALT}, - {.type = INST_LOAD_CTX, .ctx = 32}, + {.type = INST_LOAD_CTX, .ctx = 64}, {.type = INST_STORE_OVER_NAN, .slot = 13}, {.type = INST_LOAD_CTX, .ctx = 1073741824}, {.type = INST_STORE, .slot = 15}, - {.type = INST_LOAD_CTX, .ctx = 40}, + {.type = INST_LOAD_CTX, .ctx = 72}, {.type = INST_STORE, .slot = 14}, {.type = INST_LOAD_CTX, .ctx = 1073741828}, {.type = INST_STORE_OVER_NAN, .slot = 16}, @@ -425,6 +426,7 @@ static struct script *script_template__appear(int *output_slots) { output_slots[12] = -1; output_slots[13] = -1; output_slots[14] = -1; + output_slots[15] = -1; return ret; } @@ -619,6 +621,7 @@ static struct script *script_template__slide_out(int *output_slots) { output_slots[12] = 6; output_slots[13] = 7; output_slots[14] = 8; + output_slots[15] = -1; return ret; } @@ -819,6 +822,7 @@ static struct script *script_template__slide_in(int *output_slots) { output_slots[12] = 6; output_slots[13] = 7; output_slots[14] = 8; + output_slots[15] = -1; return ret; } @@ -1016,6 +1020,7 @@ static struct script *script_template__fly_out(int *output_slots) { output_slots[12] = -1; output_slots[13] = -1; output_slots[14] = -1; + output_slots[15] = -1; return ret; } @@ -1204,6 +1209,7 @@ static struct script *script_template__fly_in(int *output_slots) { output_slots[12] = -1; output_slots[13] = -1; output_slots[14] = -1; + output_slots[15] = -1; return ret; } @@ -1251,6 +1257,304 @@ static bool win_script_preset__fly_in(struct win_script *output, config_setting_ script_specialize(output->script, spec, ARR_SIZE(spec)); return true; } +static struct script *script_template__geometry_change(int *output_slots) { + static const struct instruction instrs[] = { + {.type = INST_BRANCH_ONCE, .rel = 76}, + {.type = INST_IMM, .imm = 0x1p+0}, + {.type = INST_LOAD, .slot = 11}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_LOAD, .slot = 9}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_LOAD, .slot = 12}, + {.type = INST_OP, .op = OP_DIV}, + { + .type = INST_CURVE, + .curve = {.type = CURVE_CUBIC_BEZIER, + .bezier = {.ax = 0x1.35c28f5c28f5cp+0, + .bx = -0x1.ae147ae147ae2p-2, + .cx = 0x1.ae147ae147ae2p-3, + .ay = -0x1.999999999996p-5, + .by = -0x1.cccccccccccd4p-1, + .cy = 0x1.f333333333335p+0}}, + }, + {.type = INST_OP, .op = OP_MUL}, + {.type = INST_LOAD, .slot = 11}, + {.type = INST_OP, .op = OP_ADD}, + {.type = INST_STORE, .slot = 0}, + {.type = INST_IMM, .imm = 0x1p+0}, + {.type = INST_LOAD, .slot = 13}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_LOAD, .slot = 9}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_LOAD, .slot = 14}, + {.type = INST_OP, .op = OP_DIV}, + { + .type = INST_CURVE, + .curve = {.type = CURVE_CUBIC_BEZIER, + .bezier = {.ax = 0x1.35c28f5c28f5cp+0, + .bx = -0x1.ae147ae147ae2p-2, + .cx = 0x1.ae147ae147ae2p-3, + .ay = -0x1.999999999996p-5, + .by = -0x1.cccccccccccd4p-1, + .cy = 0x1.f333333333335p+0}}, + }, + {.type = INST_OP, .op = OP_MUL}, + {.type = INST_LOAD, .slot = 13}, + {.type = INST_OP, .op = OP_ADD}, + {.type = INST_STORE, .slot = 1}, + {.type = INST_LOAD, .slot = 0}, + {.type = INST_STORE, .slot = 2}, + {.type = INST_LOAD, .slot = 1}, + {.type = INST_STORE, .slot = 3}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_LOAD, .slot = 15}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_LOAD, .slot = 9}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_LOAD, .slot = 16}, + {.type = INST_OP, .op = OP_DIV}, + { + .type = INST_CURVE, + .curve = {.type = CURVE_CUBIC_BEZIER, + .bezier = {.ax = 0x1.35c28f5c28f5cp+0, + .bx = -0x1.ae147ae147ae2p-2, + .cx = 0x1.ae147ae147ae2p-3, + .ay = -0x1.999999999996p-5, + .by = -0x1.cccccccccccd4p-1, + .cy = 0x1.f333333333335p+0}}, + }, + {.type = INST_OP, .op = OP_MUL}, + {.type = INST_LOAD, .slot = 15}, + {.type = INST_OP, .op = OP_ADD}, + {.type = INST_STORE, .slot = 4}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_LOAD, .slot = 17}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_LOAD, .slot = 9}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_LOAD, .slot = 18}, + {.type = INST_OP, .op = OP_DIV}, + { + .type = INST_CURVE, + .curve = {.type = CURVE_CUBIC_BEZIER, + .bezier = {.ax = 0x1.35c28f5c28f5cp+0, + .bx = -0x1.ae147ae147ae2p-2, + .cx = 0x1.ae147ae147ae2p-3, + .ay = -0x1.999999999996p-5, + .by = -0x1.cccccccccccd4p-1, + .cy = 0x1.f333333333335p+0}}, + }, + {.type = INST_OP, .op = OP_MUL}, + {.type = INST_LOAD, .slot = 17}, + {.type = INST_OP, .op = OP_ADD}, + {.type = INST_STORE, .slot = 5}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_LOAD, .slot = 19}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_LOAD, .slot = 9}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_LOAD, .slot = 20}, + {.type = INST_OP, .op = OP_DIV}, + { + .type = INST_CURVE, + .curve = {.type = CURVE_LINEAR}, + }, + {.type = INST_OP, .op = OP_MUL}, + {.type = INST_LOAD, .slot = 19}, + {.type = INST_OP, .op = OP_ADD}, + {.type = INST_STORE, .slot = 6}, + {.type = INST_LOAD, .slot = 4}, + {.type = INST_STORE, .slot = 7}, + {.type = INST_LOAD, .slot = 5}, + {.type = INST_STORE, .slot = 8}, + {.type = INST_BRANCH_ONCE, .rel = 31}, + {.type = INST_HALT}, + {.type = INST_LOAD_CTX, .ctx = 48}, + {.type = INST_LOAD_CTX, .ctx = 16}, + {.type = INST_OP, .op = OP_DIV}, + {.type = INST_STORE_OVER_NAN, .slot = 11}, + {.type = INST_LOAD_CTX, .ctx = 1073741824}, + {.type = INST_STORE, .slot = 12}, + {.type = INST_LOAD_CTX, .ctx = 56}, + {.type = INST_LOAD_CTX, .ctx = 24}, + {.type = INST_OP, .op = OP_DIV}, + {.type = INST_STORE_OVER_NAN, .slot = 13}, + {.type = INST_LOAD_CTX, .ctx = 1073741824}, + {.type = INST_STORE, .slot = 14}, + {.type = INST_LOAD_CTX, .ctx = 32}, + {.type = INST_LOAD_CTX, .ctx = 0}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_STORE_OVER_NAN, .slot = 15}, + {.type = INST_LOAD_CTX, .ctx = 1073741824}, + {.type = INST_STORE, .slot = 16}, + {.type = INST_LOAD_CTX, .ctx = 40}, + {.type = INST_LOAD_CTX, .ctx = 8}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_STORE_OVER_NAN, .slot = 17}, + {.type = INST_LOAD_CTX, .ctx = 1073741824}, + {.type = INST_STORE, .slot = 18}, + {.type = INST_IMM, .imm = 0x1p+0}, + {.type = INST_STORE_OVER_NAN, .slot = 19}, + {.type = INST_LOAD_CTX, .ctx = 1073741824}, + {.type = INST_STORE, .slot = 20}, + {.type = INST_BRANCH, .rel = -103}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_STORE, .slot = 10}, + {.type = INST_LOAD, .slot = 12}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_OP, .op = OP_ADD}, + {.type = INST_LOAD, .slot = 10}, + {.type = INST_OP, .op = OP_MAX}, + {.type = INST_STORE, .slot = 10}, + {.type = INST_LOAD, .slot = 14}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_OP, .op = OP_ADD}, + {.type = INST_LOAD, .slot = 10}, + {.type = INST_OP, .op = OP_MAX}, + {.type = INST_STORE, .slot = 10}, + {.type = INST_LOAD, .slot = 16}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_OP, .op = OP_ADD}, + {.type = INST_LOAD, .slot = 10}, + {.type = INST_OP, .op = OP_MAX}, + {.type = INST_STORE, .slot = 10}, + {.type = INST_LOAD, .slot = 18}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_OP, .op = OP_ADD}, + {.type = INST_LOAD, .slot = 10}, + {.type = INST_OP, .op = OP_MAX}, + {.type = INST_STORE, .slot = 10}, + {.type = INST_LOAD, .slot = 20}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_OP, .op = OP_ADD}, + {.type = INST_LOAD, .slot = 10}, + {.type = INST_OP, .op = OP_MAX}, + {.type = INST_STORE, .slot = 10}, + {.type = INST_HALT}, + }; + struct script *ret = malloc(offsetof(struct script, instrs) + sizeof(instrs)); + ret->len = ARR_SIZE(instrs); + ret->elapsed_slot = 9; + ret->n_slots = 21; + ret->stack_size = 3; + ret->vars = NULL; + ret->overrides = NULL; + memcpy(ret->instrs, instrs, sizeof(instrs)); + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("scale-x"), .slot = 0, .index = 0}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("scale-y"), .slot = 1, .index = 1}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("shadow-scale-x"), .slot = 2, .index = 2}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("shadow-scale-y"), .slot = 3, .index = 3}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("offset-x"), .slot = 4, .index = 4}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("offset-y"), .slot = 5, .index = 5}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("saved-image-blend"), .slot = 6, .index = 6}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("shadow-offset-x"), .slot = 7, .index = 7}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("shadow-offset-y"), .slot = 8, .index = 8}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct overridable_slot *override = malloc(sizeof(*override)); + *override = (struct overridable_slot){.name = strdup("scale-x"), .slot = 11}; + HASH_ADD_STR(ret->overrides, name, override); + } + { + struct overridable_slot *override = malloc(sizeof(*override)); + *override = (struct overridable_slot){.name = strdup("scale-y"), .slot = 13}; + HASH_ADD_STR(ret->overrides, name, override); + } + { + struct overridable_slot *override = malloc(sizeof(*override)); + *override = (struct overridable_slot){.name = strdup("offset-x"), .slot = 15}; + HASH_ADD_STR(ret->overrides, name, override); + } + { + struct overridable_slot *override = malloc(sizeof(*override)); + *override = (struct overridable_slot){.name = strdup("offset-y"), .slot = 17}; + HASH_ADD_STR(ret->overrides, name, override); + } + { + struct overridable_slot *override = malloc(sizeof(*override)); + *override = (struct overridable_slot){.name = strdup("saved-image-blend"), + .slot = 19}; + HASH_ADD_STR(ret->overrides, name, override); + } + output_slots[0] = 4; + output_slots[1] = 5; + output_slots[2] = 7; + output_slots[3] = 8; + output_slots[4] = -1; + output_slots[5] = -1; + output_slots[6] = -1; + output_slots[7] = 0; + output_slots[8] = 1; + output_slots[9] = 2; + output_slots[10] = 3; + output_slots[11] = -1; + output_slots[12] = -1; + output_slots[13] = -1; + output_slots[14] = -1; + output_slots[15] = 6; + return ret; +} + +static bool +win_script_preset__geometry_change(struct win_script *output, config_setting_t *setting) { + output->script = script_template__geometry_change(output->output_indices); + double knob_duration = 0x1.999999999999ap-2; + config_setting_lookup_float(setting, "duration", &knob_duration); + struct script_specialization_context spec[] = { + {.offset = SCRIPT_CTX_PLACEHOLDER_BASE + 0, .value = knob_duration}, + }; + script_specialize(output->script, spec, ARR_SIZE(spec)); + return true; +} struct { const char *name; bool (*func)(struct win_script *output, config_setting_t *setting); @@ -1261,5 +1565,6 @@ struct { {"slide-in", win_script_preset__slide_in}, {"fly-out", win_script_preset__fly_out}, {"fly-in", win_script_preset__fly_in}, + {"geometry-change", win_script_preset__geometry_change}, {NULL, NULL}, }; diff --git a/src/wm/defs.h b/src/wm/defs.h index 7c2b8421c7..9ab4305f72 100644 --- a/src/wm/defs.h +++ b/src/wm/defs.h @@ -101,5 +101,8 @@ enum win_script_output { WIN_SCRIPT_CROP_WIDTH, /// Height of the crop box WIN_SCRIPT_CROP_HEIGHT, + /// How much to blend in the saved window image + WIN_SCRIPT_SAVED_IMAGE_BLEND, + + NUM_OF_WIN_SCRIPT_OUTPUTS, }; -#define NUM_OF_WIN_SCRIPT_OUTPUTS (WIN_SCRIPT_CROP_HEIGHT + 1) diff --git a/src/wm/win.c b/src/wm/win.c index e7599073e7..8349da42cd 100644 --- a/src/wm/win.c +++ b/src/wm/win.c @@ -397,6 +397,7 @@ void win_process_primary_flags(session_t *ps, struct win *w) { } // Update window geometry + w->previous.g = w->g; w->g = w->pending_g; // Whether a window is fullscreen changes based on its geometry @@ -1732,6 +1733,10 @@ struct win_script_context win_script_context_prepare(struct session *ps, struct .width = w->widthb, .height = w->heightb, .opacity = w->opacity, + .x_before = w->previous.g.x, + .y_before = w->previous.g.y, + .width_before = w->previous.g.width + w->previous.g.border_width * 2, + .height_before = w->previous.g.height + w->previous.g.border_width * 2, .opacity_before = w->previous.opacity, .monitor_x = monitor.x1, .monitor_y = monitor.y1, @@ -1762,11 +1767,39 @@ double win_animatable_get(const struct win *w, enum win_script_output output) { case WIN_SCRIPT_SHADOW_SCALE_Y: return 1; case WIN_SCRIPT_CROP_WIDTH: case WIN_SCRIPT_CROP_HEIGHT: return INFINITY; + case WIN_SCRIPT_SAVED_IMAGE_BLEND: return 0; + default: unreachable(); } unreachable(); } #define WSTATE_PAIR(a, b) ((int)(a) * NUM_OF_WSTATES + (int)(b)) +/// Advance the animation of a window. +/// +/// Returns true if animation was running before this function is called, and is no +/// longer running now. Returns false if animation is still running, or if there was no +/// animation running when this is called. +static bool win_advance_animation(struct win *w, double delta_t, + const struct win_script_context *win_ctx) { + // No state changes, if there's a animation running, we just continue it. + if (w->running_animation_instance == NULL) { + return false; + } + log_verbose("Advance animation for %#010x (%s) %f seconds", win_id(w), w->name, delta_t); + if (!script_instance_is_finished(w->running_animation_instance)) { + auto elapsed_slot = + script_elapsed_slot(w->running_animation_instance->script); + w->running_animation_instance->memory[elapsed_slot] += delta_t; + auto result = + script_instance_evaluate(w->running_animation_instance, (void *)win_ctx); + if (result != SCRIPT_EVAL_OK) { + log_error("Failed to run animation script: %d", result); + return true; + } + return false; + } + return true; +} bool win_process_animation_and_state_change(struct session *ps, struct win *w, double delta_t) { // If the window hasn't ever been damaged yet, it won't be rendered in this frame. @@ -1791,28 +1824,9 @@ bool win_process_animation_and_state_change(struct session *ps, struct win *w, d auto win_ctx = win_script_context_prepare(ps, w); w->previous.opacity = w->opacity; - if (w->previous.state == w->state && win_ctx.opacity_before == win_ctx.opacity) { - advance_animation: - // No state changes, if there's a animation running, we just continue it. - if (w->running_animation_instance == NULL) { - return false; - } - log_verbose("Advance animation for %#010x (%s) %f seconds", win_id(w), - w->name, delta_t); - if (!script_instance_is_finished(w->running_animation_instance)) { - auto elapsed_slot = - script_elapsed_slot(w->running_animation_instance->script); - w->running_animation_instance->memory[elapsed_slot] += delta_t; - auto result = script_instance_evaluate( - w->running_animation_instance, &win_ctx); - if (result != SCRIPT_EVAL_OK) { - log_error("Failed to run animation script: %d", result); - return true; - } - return false; - } - return true; - } + + bool geometry_changed = !win_geometry_eq(w->previous.g, w->g); + w->previous.g = w->g; // Try to determine the right animation trigger based on state changes. Note there // is some complications here. X automatically unmaps windows before destroying @@ -1823,13 +1837,10 @@ bool win_process_animation_and_state_change(struct session *ps, struct win *w, d // gap between the UnmapNotify and DestroyNotify. There is no way on our end of // fixing this without using hacks. enum animation_trigger trigger = ANIMATION_TRIGGER_INVALID; - if (w->previous.state == w->state) { - // Only opacity changed - assert(w->state == WSTATE_MAPPED); - trigger = win_ctx.opacity > win_ctx.opacity_before - ? ANIMATION_TRIGGER_INCREASE_OPACITY - : ANIMATION_TRIGGER_DECREASE_OPACITY; - } else { + + // Animation trigger priority: + // state > geometry > opacity + if (w->previous.state != w->state) { // Send D-Bus signal if (ps->o.dbus) { switch (w->state) { @@ -1875,18 +1886,34 @@ bool win_process_animation_and_state_change(struct session *ps, struct win *w, d assert(false); return true; } + } else if (geometry_changed) { + assert(w->state == WSTATE_MAPPED); + trigger = ANIMATION_TRIGGER_GEOMETRY; + } else if (win_ctx.opacity_before != win_ctx.opacity) { + assert(w->state == WSTATE_MAPPED); + trigger = win_ctx.opacity > win_ctx.opacity_before + ? ANIMATION_TRIGGER_INCREASE_OPACITY + : ANIMATION_TRIGGER_DECREASE_OPACITY; } - if (trigger != ANIMATION_TRIGGER_INVALID && w->running_animation_instance && - (w->running_animation.suppressions & (1 << trigger)) != 0) { + if (trigger == ANIMATION_TRIGGER_INVALID) { + // No state changes, if there's a animation running, we just continue it. + return win_advance_animation(w, delta_t, &win_ctx); + } else if (w->running_animation_instance && + (w->running_animation.suppressions & (1 << trigger)) != 0) { log_debug("Not starting animation %s for window %#010x (%s) because it " "is being suppressed.", animation_trigger_names[trigger], win_id(w), w->name); - goto advance_animation; + return win_advance_animation(w, delta_t, &win_ctx); + } else if (w->animation_block[trigger] > 0) { + log_debug("Not starting animation %s for window %#010x (%s) because it " + "is blocked.", + animation_trigger_names[trigger], win_id(w), w->name); + return win_advance_animation(w, delta_t, &win_ctx); } auto wopts = win_options(w); - if (trigger == ANIMATION_TRIGGER_INVALID || wopts.animations[trigger].script == NULL) { + if (wopts.animations[trigger].script == NULL) { return true; } @@ -1899,8 +1926,89 @@ bool win_process_animation_and_state_change(struct session *ps, struct win *w, d log_debug("Starting animation %s for window %#010x (%s)", animation_trigger_names[trigger], win_id(w), w->name); + if (win_check_flags_any(w, WIN_FLAGS_PIXMAP_STALE)) { + // Grab the old pixmap, animations might need it + if (w->saved_win_image) { + ps->backend_data->ops.release_image(ps->backend_data, + w->saved_win_image); + w->saved_win_image = NULL; + } + if (ps->drivers & DRIVER_NVIDIA) { + if (w->win_image != NULL) { + w->saved_win_image = ps->backend_data->ops.new_image( + ps->backend_data, BACKEND_IMAGE_FORMAT_PIXMAP, + (ivec2){ + .width = (int)win_ctx.width_before, + .height = (int)win_ctx.height_before, + }); + region_t copy_region; + pixman_region32_init_rect(©_region, 0, 0, + (uint)win_ctx.width_before, + (uint)win_ctx.height_before); + ps->backend_data->ops.copy_area( + ps->backend_data, (ivec2){}, w->saved_win_image, + w->win_image, ©_region); + pixman_region32_fini(©_region); + } + } else { + w->saved_win_image = w->win_image; + w->win_image = NULL; + } + w->saved_win_image_scale = (vec2){ + .x = win_ctx.width / win_ctx.width_before, + .y = win_ctx.height / win_ctx.height_before, + }; + } + auto new_animation = script_instance_new(wopts.animations[trigger].script); if (w->running_animation_instance) { + // Interrupt the old animation and start the new animation from where the + // old has left off. Note we still need to advance the old animation for + // the last interval. + win_advance_animation(w, delta_t, &win_ctx); + auto memory = w->running_animation_instance->memory; + auto output_indices = w->running_animation.output_indices; + if (output_indices[WIN_SCRIPT_SAVED_IMAGE_BLEND] >= 0) { + memory[output_indices[WIN_SCRIPT_SAVED_IMAGE_BLEND]] = + 1 - memory[output_indices[WIN_SCRIPT_SAVED_IMAGE_BLEND]]; + } + if (geometry_changed) { + // If the window has moved, we need to adjust scripts + // outputs so that the window will stay in the same position and + // size after applying the animation. This way the window's size + // and position won't change discontinuously. + struct { + int output; + double delta; + } adjustments[] = { + {WIN_SCRIPT_OFFSET_X, win_ctx.x_before - win_ctx.x}, + {WIN_SCRIPT_OFFSET_Y, win_ctx.y_before - win_ctx.y}, + {WIN_SCRIPT_SHADOW_OFFSET_X, win_ctx.x_before - win_ctx.x}, + {WIN_SCRIPT_SHADOW_OFFSET_Y, win_ctx.y_before - win_ctx.y}, + }; + for (size_t i = 0; i < ARR_SIZE(adjustments); i++) { + if (output_indices[adjustments[i].output] >= 0) { + memory[output_indices[adjustments[i].output]] += + adjustments[i].delta; + } + } + + struct { + int output; + double factor; + } factors[] = { + {WIN_SCRIPT_SCALE_X, win_ctx.width_before / win_ctx.width}, + {WIN_SCRIPT_SCALE_Y, win_ctx.height_before / win_ctx.height}, + {WIN_SCRIPT_SHADOW_SCALE_X, win_ctx.width_before / win_ctx.width}, + {WIN_SCRIPT_SHADOW_SCALE_Y, win_ctx.height_before / win_ctx.height}, + }; + for (size_t i = 0; i < ARR_SIZE(factors); i++) { + if (output_indices[factors[i].output] >= 0) { + memory[output_indices[factors[i].output]] *= + factors[i].factor; + } + } + } script_instance_resume_from(w->running_animation_instance, new_animation); free(w->running_animation_instance); } diff --git a/src/wm/win.h b/src/wm/win.h index 1de2370424..f852cb5fb6 100644 --- a/src/wm/win.h +++ b/src/wm/win.h @@ -90,9 +90,13 @@ struct win_geometry { /// These are changes of window state that might trigger an animation. We separate them /// out and delay their application so determining which animation to run is easier. +/// +/// These values are only hold for an instant, and once the animation is started they are +/// updated to reflect the latest state. struct win_state_change { winstate_t state; double opacity; + struct win_geometry g; }; struct win { @@ -101,6 +105,12 @@ struct win { /// backend data attached to this window. Only available when /// `state` is not UNMAPPED image_handle win_image; + /// The old window image before the window image is refreshed. This is used for + /// animation, and is only kept alive for the duration of the animation. + image_handle saved_win_image; + /// How much to scale the saved_win_image, so that it is the same size as the + /// current window image. + vec2 saved_win_image_scale; image_handle shadow_image; image_handle mask_image; // TODO(yshui) only used by legacy backends, remove. @@ -254,10 +264,14 @@ struct win { struct win_state_change previous; struct script_instance *running_animation_instance; struct win_script running_animation; + + /// Number of times each animation trigger is blocked + unsigned int animation_block[ANIMATION_TRIGGER_COUNT]; }; struct win_script_context { double x, y, width, height; + double x_before, y_before, width_before, height_before; double opacity_before, opacity; double monitor_x, monitor_y; double monitor_width, monitor_height; @@ -270,6 +284,10 @@ static const struct script_context_info win_script_context_info[] = { {"window-y", offsetof(struct win_script_context, y)}, {"window-width", offsetof(struct win_script_context, width)}, {"window-height", offsetof(struct win_script_context, height)}, + {"window-x-before", offsetof(struct win_script_context, x_before)}, + {"window-y-before", offsetof(struct win_script_context, y_before)}, + {"window-width-before", offsetof(struct win_script_context, width_before)}, + {"window-height-before", offsetof(struct win_script_context, height_before)}, {"window-raw-opacity-before", offsetof(struct win_script_context, opacity_before)}, {"window-raw-opacity", offsetof(struct win_script_context, opacity)}, {"window-monitor-x", offsetof(struct win_script_context, monitor_x)}, @@ -295,6 +313,7 @@ static const struct script_output_info win_script_outputs[] = { [WIN_SCRIPT_CROP_Y] = {"crop-y"}, [WIN_SCRIPT_CROP_WIDTH] = {"crop-width"}, [WIN_SCRIPT_CROP_HEIGHT] = {"crop-height"}, + [WIN_SCRIPT_SAVED_IMAGE_BLEND] = {"saved-image-blend"}, [NUM_OF_WIN_SCRIPT_OUTPUTS] = {NULL}, }; @@ -314,7 +333,7 @@ static const struct window_maybe_options WIN_MAYBE_OPTIONS_DEFAULT = { static inline void win_script_fold(const struct win_script *upper, const struct win_script *lower, struct win_script *output) { - for (size_t i = 0; i <= ANIMATION_TRIGGER_LAST; i++) { + for (size_t i = 0; i < ANIMATION_TRIGGER_COUNT; i++) { output[i] = upper[i].script ? upper[i] : lower[i]; } } @@ -372,6 +391,12 @@ win_options(const struct win *w) { win_maybe_options_fold(w->options_override, w->options), *w->options_default); } +/// Check if win_geometry `a` and `b` have the same sizes and positions. Border width is +/// not considered. +static inline bool win_geometry_eq(struct win_geometry a, struct win_geometry b) { + return a.x == b.x && a.y == b.y && a.width == b.width && a.height == b.height; +} + /// Process pending updates/images flags on a window. Has to be called in X critical /// section. Returns true if the window had an animation running and it has just finished, /// or if the window's states just changed and there is no animation defined for this