Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Animation part 3: per-window animations #1303

Merged
merged 4 commits into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions src/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,12 @@ static const char *animation_trigger_names[] attr_unused = {

struct script;
struct win_script {
int output_indices[NUM_OF_WIN_SCRIPT_OUTPUTS];
/// A running animation can be configured to prevent other animations from
/// starting.
uint64_t suppressions;
struct script *script;
/// true if this script is generated by us, false if this is a user choice.
int output_indices[NUM_OF_WIN_SCRIPT_OUTPUTS];
bool is_generated;
};

Expand Down Expand Up @@ -192,6 +192,9 @@ struct window_maybe_options {
enum window_unredir_option unredir;
/// Whether shadow should be rendered beneath this window.
enum tristate full_shadow;

/// Window specific animations
struct win_script animations[ANIMATION_TRIGGER_LAST + 1];
};

// Make sure `window_options` has no implicit padding.
Expand All @@ -212,12 +215,16 @@ struct window_options {
bool clip_shadow_above;
bool paint;
bool full_shadow;

struct win_script animations[ANIMATION_TRIGGER_LAST + 1];
};
#pragma GCC diagnostic pop

static inline bool
win_options_eq(const struct window_options *a, const struct window_options *b) {
return memcmp(a, b, sizeof(struct window_options)) == 0;
win_options_no_damage(const struct window_options *a, const struct window_options *b) {
// Animation changing does not immediately change how window is rendered, so
// they don't cause damage.
return memcmp(a, b, offsetof(struct window_options, animations)) == 0;
}

extern struct shader_info null_shader;
Expand Down
21 changes: 12 additions & 9 deletions src/config_libconfig.c
Original file line number Diff line number Diff line change
Expand Up @@ -580,7 +580,7 @@
{"transparent-clipping", offsetof(struct window_maybe_options, transparent_clipping)},
};

static c2_lptr_t *parse_rule(config_setting_t *setting) {
static c2_lptr_t *parse_rule(config_setting_t *setting, struct script ***out_scripts) {
if (!config_setting_is_group(setting)) {
log_error("Invalid rule at line %d. It must be a group.",
config_setting_source_line(setting));
Expand All @@ -603,10 +603,7 @@
}

auto wopts = cmalloc(struct window_maybe_options);
*wopts = (struct window_maybe_options){
.opacity = NAN,
.corner_radius = -1,
};
*wopts = WIN_MAYBE_OPTIONS_DEFAULT;
c2_list_set_data(rule, wopts);

for (size_t i = 0; i < ARR_SIZE(all_window_options); i++) {
Expand All @@ -625,14 +622,20 @@
wopts->corner_radius = ival;
}

auto unredir_setting = config_setting_lookup(setting, "unredir-if-possible");
auto unredir_setting = config_setting_lookup(setting, "unredir");
if (unredir_setting) {
wopts->unredir = parse_unredir_option(unredir_setting);
}

auto animations = config_setting_lookup(setting, "animations");
if (animations) {
parse_animations(wopts->animations, animations, out_scripts);

Check warning on line 632 in src/config_libconfig.c

View check run for this annotation

Codecov / codecov/patch

src/config_libconfig.c#L632

Added line #L632 was not covered by tests
}
return rule;
}

static void parse_rules(config_setting_t *setting, c2_lptr_t **rules) {
static void
parse_rules(config_setting_t *setting, struct script ***out_scripts, c2_lptr_t **rules) {
if (!config_setting_is_list(setting)) {
log_error("Invalid value for \"rules\" at line %d. It must be a list.",
config_setting_source_line(setting));
Expand All @@ -641,7 +644,7 @@
const auto length = (unsigned int)config_setting_length(setting);
for (unsigned int i = 0; i < length; i++) {
auto sub = config_setting_get_elem(setting, i);
auto rule = parse_rule(sub);
auto rule = parse_rule(sub, out_scripts);
if (rule != NULL) {
c2_condlist_insert(rules, rule);
}
Expand Down Expand Up @@ -767,7 +770,7 @@

config_setting_t *rules = config_lookup(&cfg, "rules");
if (rules) {
parse_rules(rules, &opt->rules);
parse_rules(rules, &opt->all_scripts, &opt->rules);
}

// --dbus
Expand Down
26 changes: 14 additions & 12 deletions src/picom.c
Original file line number Diff line number Diff line change
Expand Up @@ -463,10 +463,10 @@ static void destroy_backend(session_t *ps) {
// `win_process_animation_and_state_change` hasn't been called.)
// TBH, this assertion is probably too complex than what it's worth.
assert(!w->win_image || w->state == WSTATE_MAPPED ||
w->running_animation != NULL || w->previous.state != w->state);
w->running_animation_instance != NULL || w->previous.state != w->state);
// Wrapping up animation in progress
free(w->running_animation);
w->running_animation = NULL;
free(w->running_animation_instance);
w->running_animation_instance = NULL;

if (ps->backend_data) {
// Unmapped windows could still have shadow images.
Expand Down Expand Up @@ -751,15 +751,15 @@ static bool paint_preprocess(session_t *ps, bool *animation, struct win **out_bo
const winmode_t mode_old = w->mode;
const bool was_painted = w->to_paint;

if (w->running_animation != NULL) {
if (w->running_animation_instance != NULL) {
*animation = true;
}

// Add window to damaged area if its opacity changes
// If was_painted == false, and to_paint is also false, we don't care
// If was_painted == false, but to_paint is true, damage will be added in
// the loop below
if (was_painted && w->running_animation != NULL) {
if (was_painted && w->running_animation_instance != NULL) {
add_damage_from_win(ps, w);
}

Expand Down Expand Up @@ -817,7 +817,7 @@ static bool paint_preprocess(session_t *ps, bool *animation, struct win **out_bo
// pixmap is gone (for example due to a ConfigureNotify), or when it's
// excluded
if ((w->state == WSTATE_UNMAPPED || w->state == WSTATE_DESTROYED) &&
w->running_animation == NULL) {
w->running_animation_instance == NULL) {
log_trace("|- is unmapped");
to_paint = false;
} else if (unlikely(ps->debug_window != XCB_NONE) &&
Expand Down Expand Up @@ -929,7 +929,7 @@ static bool paint_preprocess(session_t *ps, bool *animation, struct win **out_bo
reg_ignore_valid = reg_ignore_valid && w->reg_ignore_valid;
w->reg_ignore_valid = true;

if (w->state == WSTATE_DESTROYED && w->running_animation == NULL) {
if (w->state == WSTATE_DESTROYED && w->running_animation_instance == NULL) {
// the window should be destroyed because it was destroyed
// by X server and now its animations are finished
win_destroy_finish(ps, w);
Expand Down Expand Up @@ -1662,8 +1662,8 @@ static void handle_pending_updates(struct session *ps, double delta_t) {
// Window might be freed by this function, if it's destroyed and its
// animation finished
if (w != NULL && win_process_animation_and_state_change(ps, w, delta_t)) {
free(w->running_animation);
w->running_animation = NULL;
free(w->running_animation_instance);
w->running_animation_instance = NULL;
w->in_openclose = false;
if (w->state == WSTATE_UNMAPPED) {
unmap_win_finish(ps, w);
Expand Down Expand Up @@ -1738,8 +1738,8 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) {
if (w == NULL) {
continue;
}
free(w->running_animation);
w->running_animation = NULL;
free(w->running_animation_instance);
w->running_animation_instance = NULL;
if (w->state == WSTATE_DESTROYED) {
win_destroy_finish(ps, w);
}
Expand Down Expand Up @@ -1991,7 +1991,7 @@ static bool load_shader_source_for_condition(const c2_lptr_t *cond, void *data)
}

static struct window_options win_options_from_config(const struct options *opts) {
return (struct window_options){
struct window_options ret = {
.blur_background = opts->blur_method != BLUR_METHOD_NONE,
.full_shadow = false,
.shadow = opts->shadow_enable,
Expand All @@ -2006,6 +2006,8 @@ static struct window_options win_options_from_config(const struct options *opts)
.unredir = WINDOW_UNREDIR_WHEN_POSSIBLE_ELSE_TERMINATE,
.opacity = 1,
};
memcpy(ret.animations, opts->animations, sizeof(ret.animations));
return ret;
}

/**
Expand Down
49 changes: 25 additions & 24 deletions src/wm/win.c
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,7 @@ void win_process_secondary_flags(session_t *ps, struct win *w) {
}

auto new_options = win_options(w);
if (win_options_eq(&old_options, &new_options)) {
if (win_options_no_damage(&old_options, &new_options)) {
pixman_region32_fini(&extents);
return;
}
Expand Down Expand Up @@ -792,7 +792,7 @@ void unmap_win_finish(session_t *ps, struct win *w) {
if (w->state != WSTATE_DESTROYED) {
win_clear_flags(w, WIN_FLAGS_PIXMAP_ERROR);
}
assert(w->running_animation == NULL);
assert(w->running_animation_instance == NULL);
}

/**
Expand Down Expand Up @@ -1715,8 +1715,9 @@ struct win_script_context win_script_context_prepare(struct session *ps, struct
}

double win_animatable_get(const struct win *w, enum win_script_output output) {
if (w->running_animation && w->running_animation_outputs[output] >= 0) {
return w->running_animation->memory[w->running_animation_outputs[output]];
if (w->running_animation_instance && w->running_animation.output_indices[output] >= 0) {
return w->running_animation_instance
->memory[w->running_animation.output_indices[output]];
}
switch (output) {
case WIN_SCRIPT_BLUR_OPACITY: return w->state == WSTATE_MAPPED ? 1.0 : 0.0;
Expand Down Expand Up @@ -1758,23 +1759,23 @@ bool win_process_animation_and_state_change(struct session *ps, struct win *w, d
bool state_changed = w->previous.state != w->state;
w->previous.state = w->state;
w->previous.opacity = w->opacity;
return state_changed || (w->running_animation != NULL);
return state_changed || (w->running_animation_instance != NULL);
}

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 == NULL) {
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)) {
w->running_animation->elapsed += delta_t;
auto result =
script_instance_evaluate(w->running_animation, &win_ctx);
if (!script_instance_is_finished(w->running_animation_instance)) {
w->running_animation_instance->elapsed += 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;
Expand Down Expand Up @@ -1824,7 +1825,7 @@ bool win_process_animation_and_state_change(struct session *ps, struct win *w, d
break;
case WSTATE_PAIR(WSTATE_UNMAPPED, WSTATE_DESTROYED):
if ((!ps->o.no_fading_destroyed_argb || !win_has_alpha(w)) &&
w->running_animation != NULL) {
w->running_animation_instance != NULL) {
trigger = ANIMATION_TRIGGER_CLOSE;
}
break;
Expand All @@ -1847,19 +1848,20 @@ bool win_process_animation_and_state_change(struct session *ps, struct win *w, d
}
}

if (trigger != ANIMATION_TRIGGER_INVALID && w->running_animation &&
(w->running_animation_suppressions & (1 << trigger)) != 0) {
if (trigger != ANIMATION_TRIGGER_INVALID && 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;
}

if (trigger == ANIMATION_TRIGGER_INVALID || ps->o.animations[trigger].script == NULL) {
auto wopts = win_options(w);
if (trigger == ANIMATION_TRIGGER_INVALID || wopts.animations[trigger].script == NULL) {
return true;
}

if (ps->o.animations[trigger].is_generated && !win_options(w).fade) {
if (wopts.animations[trigger].is_generated && !wopts.fade) {
// Window's animation is fading (as signified by the fact that it's
// generated), but the user has disabled fading for this window.
return true;
Expand All @@ -1868,16 +1870,15 @@ 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);

auto new_animation = script_instance_new(ps->o.animations[trigger].script);
if (w->running_animation) {
script_instance_resume_from(w->running_animation, new_animation);
free(w->running_animation);
auto new_animation = script_instance_new(wopts.animations[trigger].script);
if (w->running_animation_instance) {
script_instance_resume_from(w->running_animation_instance, new_animation);
free(w->running_animation_instance);
}
w->running_animation = new_animation;
w->running_animation_outputs = ps->o.animations[trigger].output_indices;
w->running_animation_suppressions = ps->o.animations[trigger].suppressions;
script_instance_evaluate(w->running_animation, &win_ctx);
return false;
w->running_animation_instance = new_animation;
w->running_animation = wopts.animations[trigger];
script_instance_evaluate(w->running_animation_instance, &win_ctx);
return script_instance_is_finished(w->running_animation_instance);
}

#undef WSTATE_PAIR
Expand Down
20 changes: 15 additions & 5 deletions src/wm/win.h
Original file line number Diff line number Diff line change
Expand Up @@ -251,9 +251,8 @@
/// by `win_process_animation_and_state_change` to trigger appropriate
/// animations.
struct win_state_change previous;
struct script_instance *running_animation;
const int *running_animation_outputs;
uint64_t running_animation_suppressions;
struct script_instance *running_animation_instance;
struct win_script running_animation;
};

struct win_script_context {
Expand Down Expand Up @@ -310,12 +309,19 @@
.unredir = WINDOW_UNREDIR_INVALID,
};

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++) {
output[i] = upper[i].script ? upper[i] : lower[i];

Check warning on line 315 in src/wm/win.h

View check run for this annotation

Codecov / codecov/patch

src/wm/win.h#L315

Added line #L315 was not covered by tests
}
}

/// Combine two window options. The `upper` value has higher priority, the `lower` value
/// will only be used if the corresponding value in `upper` is not set (e.g. it is
/// TRI_UNKNOWN for tristate values, NaN for opacity, -1 for corner_radius).
static inline struct window_maybe_options __attribute__((always_inline))
win_maybe_options_fold(struct window_maybe_options upper, struct window_maybe_options lower) {
return (struct window_maybe_options){
struct window_maybe_options ret = {
.unredir = upper.unredir == WINDOW_UNREDIR_INVALID ? lower.unredir : upper.unredir,
.blur_background = tri_or(upper.blur_background, lower.blur_background),
.clip_shadow_above = tri_or(upper.clip_shadow_above, lower.clip_shadow_above),
Expand All @@ -329,14 +335,16 @@
.shader = upper.shader ? upper.shader : lower.shader,
.corner_radius = upper.corner_radius >= 0 ? upper.corner_radius : lower.corner_radius,
};
win_script_fold(upper.animations, lower.animations, ret.animations);
return ret;
}

/// Unwrap a `window_maybe_options` to a `window_options`, using the default value for
/// values that are not set in the `window_maybe_options`.
static inline struct window_options __attribute__((always_inline))
win_maybe_options_or(struct window_maybe_options maybe, struct window_options def) {
assert(def.unredir != WINDOW_UNREDIR_INVALID);
return (struct window_options){
struct window_options ret = {
.unredir = maybe.unredir == WINDOW_UNREDIR_INVALID ? def.unredir : maybe.unredir,
.blur_background = tri_or_bool(maybe.blur_background, def.blur_background),
.clip_shadow_above = tri_or_bool(maybe.clip_shadow_above, def.clip_shadow_above),
Expand All @@ -351,6 +359,8 @@
.dim = !safe_isnan(maybe.dim) ? maybe.dim : def.dim,
.shader = maybe.shader ? maybe.shader : def.shader,
};
win_script_fold(maybe.animations, def.animations, ret.animations);
return ret;
}

static inline struct window_options __attribute__((always_inline))
Expand Down