Skip to content

Commit f3ce425

Browse files
committed
animation: add geometry animation trigger
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
1 parent c0c13be commit f3ce425

File tree

8 files changed

+104
-30
lines changed

8 files changed

+104
-30
lines changed

man/picom.1.adoc

+6
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,10 @@ animations = ({
515515

516516
_decrease-opacity_:: When the opacity of a window is decreased.
517517

518+
_geometry_:: When the geometry of a window is changed. (EXPERIMENTAL)
519+
+
520+
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.
521+
518522
_suppressions_:::
519523
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.
520524

@@ -713,6 +717,8 @@ Currently, these context variables are defined: :::
713717

714718
_window-width_, _window-height_:: The size of the window.
715719

720+
_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.
721+
716722
_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.
717723

718724
_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.

src/config.h

+9-5
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ enum vblank_scheduler_type {
7070
};
7171

7272
enum animation_trigger {
73-
ANIMATION_TRIGGER_INVALID = -1,
7473
/// When a hidden window is shown
7574
ANIMATION_TRIGGER_SHOW = 0,
7675
/// When a window is hidden
@@ -83,7 +82,11 @@ enum animation_trigger {
8382
ANIMATION_TRIGGER_OPEN,
8483
/// When a window is closed
8584
ANIMATION_TRIGGER_CLOSE,
86-
ANIMATION_TRIGGER_LAST = ANIMATION_TRIGGER_CLOSE,
85+
/// When a window's geometry changes
86+
ANIMATION_TRIGGER_GEOMETRY,
87+
88+
ANIMATION_TRIGGER_INVALID,
89+
ANIMATION_TRIGGER_COUNT = ANIMATION_TRIGGER_INVALID,
8790
};
8891

8992
static const char *animation_trigger_names[] attr_unused = {
@@ -93,6 +96,7 @@ static const char *animation_trigger_names[] attr_unused = {
9396
[ANIMATION_TRIGGER_DECREASE_OPACITY] = "decrease-opacity",
9497
[ANIMATION_TRIGGER_OPEN] = "open",
9598
[ANIMATION_TRIGGER_CLOSE] = "close",
99+
[ANIMATION_TRIGGER_GEOMETRY] = "geometry",
96100
};
97101

98102
struct script;
@@ -192,7 +196,7 @@ struct window_maybe_options {
192196
enum tristate full_shadow;
193197

194198
/// Window specific animations
195-
struct win_script animations[ANIMATION_TRIGGER_LAST + 1];
199+
struct win_script animations[ANIMATION_TRIGGER_COUNT];
196200
};
197201

198202
// Make sure `window_options` has no implicit padding.
@@ -214,7 +218,7 @@ struct window_options {
214218
bool paint;
215219
bool full_shadow;
216220

217-
struct win_script animations[ANIMATION_TRIGGER_LAST + 1];
221+
struct win_script animations[ANIMATION_TRIGGER_COUNT];
218222
};
219223
#pragma GCC diagnostic pop
220224

@@ -432,7 +436,7 @@ typedef struct options {
432436

433437
bool dithered_present;
434438
// === Animation ===
435-
struct win_script animations[ANIMATION_TRIGGER_LAST + 1];
439+
struct win_script animations[ANIMATION_TRIGGER_COUNT];
436440
/// Array of all the scripts used in `animations`. This is a dynarr.
437441
struct script **all_scripts;
438442

src/config_libconfig.c

+3-3
Original file line numberDiff line numberDiff line change
@@ -224,8 +224,8 @@ static inline void parse_wintype_config(const config_t *cfg, const char *member_
224224
}
225225
}
226226

227-
static enum animation_trigger parse_animation_trigger(const char *trigger) {
228-
for (int i = 0; i <= ANIMATION_TRIGGER_LAST; i++) {
227+
enum animation_trigger parse_animation_trigger(const char *trigger) {
228+
for (unsigned i = 0; i < ANIMATION_TRIGGER_COUNT; i++) {
229229
if (strcasecmp(trigger, animation_trigger_names[i]) == 0) {
230230
return i;
231231
}
@@ -297,7 +297,7 @@ static bool parse_animation_one(struct win_script *animations,
297297
}
298298
auto number_of_triggers =
299299
config_setting_get_string(triggers) == NULL ? config_setting_length(triggers) : 1;
300-
if (number_of_triggers > ANIMATION_TRIGGER_LAST) {
300+
if (number_of_triggers >= ANIMATION_TRIGGER_INVALID) {
301301
log_error("Too many triggers in animation defined at line %d",
302302
config_setting_source_line(triggers));
303303
return false;

src/dbus.c

+1-3
Original file line numberDiff line numberDiff line change
@@ -555,9 +555,7 @@ cdbus_process_list_win(session_t *ps, DBusMessage *msg attr_unused, DBusMessage
555555
return DBUS_HANDLER_RESULT_HANDLED;
556556
}
557557

558-
/**
559-
* Process a win_get D-Bus request.
560-
*/
558+
/// Process a property Get D-Bus request.
561559
static DBusHandlerResult
562560
cdbus_process_window_property_get(session_t *ps, DBusMessage *msg, cdbus_window_t wid,
563561
DBusMessage *reply, DBusError *e) {

src/inspect.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ void inspect_dump_window_maybe_options(struct window_maybe_options wopts) {
218218
}
219219

220220
char **animation_triggers = dynarr_new(char *, 0);
221-
for (int i = 0; i <= ANIMATION_TRIGGER_LAST; i++) {
221+
for (int i = 0; i < ANIMATION_TRIGGER_COUNT; i++) {
222222
if (wopts.animations[i].script != NULL) {
223223
char *name = NULL;
224224
casprintf(&name, "\"%s\"", animation_trigger_names[i]);

src/transition/generated/script_templates.c

+4-4
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,11 @@ static struct script *script_template__disappear(int *output_slots) {
8181
{.type = INST_STORE, .slot = 10},
8282
{.type = INST_BRANCH_ONCE, .rel = 15},
8383
{.type = INST_HALT},
84-
{.type = INST_LOAD_CTX, .ctx = 32},
84+
{.type = INST_LOAD_CTX, .ctx = 64},
8585
{.type = INST_STORE_OVER_NAN, .slot = 13},
8686
{.type = INST_LOAD_CTX, .ctx = 1073741824},
8787
{.type = INST_STORE, .slot = 15},
88-
{.type = INST_LOAD_CTX, .ctx = 40},
88+
{.type = INST_LOAD_CTX, .ctx = 72},
8989
{.type = INST_STORE, .slot = 14},
9090
{.type = INST_IMM, .imm = 0x1p+0},
9191
{.type = INST_STORE_OVER_NAN, .slot = 16},
@@ -299,11 +299,11 @@ static struct script *script_template__appear(int *output_slots) {
299299
{.type = INST_STORE, .slot = 10},
300300
{.type = INST_BRANCH_ONCE, .rel = 13},
301301
{.type = INST_HALT},
302-
{.type = INST_LOAD_CTX, .ctx = 32},
302+
{.type = INST_LOAD_CTX, .ctx = 64},
303303
{.type = INST_STORE_OVER_NAN, .slot = 13},
304304
{.type = INST_LOAD_CTX, .ctx = 1073741824},
305305
{.type = INST_STORE, .slot = 15},
306-
{.type = INST_LOAD_CTX, .ctx = 40},
306+
{.type = INST_LOAD_CTX, .ctx = 72},
307307
{.type = INST_STORE, .slot = 14},
308308
{.type = INST_LOAD_CTX, .ctx = 1073741828},
309309
{.type = INST_STORE_OVER_NAN, .slot = 16},

src/wm/win.c

+64-13
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,7 @@ void win_process_primary_flags(session_t *ps, struct win *w) {
397397
}
398398

399399
// Update window geometry
400+
w->previous.g = w->g;
400401
w->g = w->pending_g;
401402

402403
// 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
17321733
.width = w->widthb,
17331734
.height = w->heightb,
17341735
.opacity = w->opacity,
1736+
.x_before = w->previous.g.x,
1737+
.y_before = w->previous.g.y,
1738+
.width_before = w->previous.g.width + w->previous.g.border_width * 2,
1739+
.height_before = w->previous.g.height + w->previous.g.border_width * 2,
17351740
.opacity_before = w->previous.opacity,
17361741
.monitor_x = monitor.x1,
17371742
.monitor_y = monitor.y1,
@@ -1817,10 +1822,9 @@ bool win_process_animation_and_state_change(struct session *ps, struct win *w, d
18171822

18181823
auto win_ctx = win_script_context_prepare(ps, w);
18191824
w->previous.opacity = w->opacity;
1820-
if (w->previous.state == w->state && win_ctx.opacity_before == win_ctx.opacity) {
1821-
// No state changes, if there's a animation running, we just continue it.
1822-
return win_advance_animation(w, delta_t, &win_ctx);
1823-
}
1825+
1826+
bool geometry_changed = !win_geometry_eq(w->previous.g, w->g);
1827+
w->previous.g = w->g;
18241828

18251829
// Try to determine the right animation trigger based on state changes. Note there
18261830
// is some complications here. X automatically unmaps windows before destroying
@@ -1831,13 +1835,10 @@ bool win_process_animation_and_state_change(struct session *ps, struct win *w, d
18311835
// gap between the UnmapNotify and DestroyNotify. There is no way on our end of
18321836
// fixing this without using hacks.
18331837
enum animation_trigger trigger = ANIMATION_TRIGGER_INVALID;
1834-
if (w->previous.state == w->state) {
1835-
// Only opacity changed
1836-
assert(w->state == WSTATE_MAPPED);
1837-
trigger = win_ctx.opacity > win_ctx.opacity_before
1838-
? ANIMATION_TRIGGER_INCREASE_OPACITY
1839-
: ANIMATION_TRIGGER_DECREASE_OPACITY;
1840-
} else {
1838+
1839+
// Animation trigger priority:
1840+
// state > geometry > opacity
1841+
if (w->previous.state != w->state) {
18411842
// Send D-Bus signal
18421843
if (ps->o.dbus) {
18431844
switch (w->state) {
@@ -1883,10 +1884,21 @@ bool win_process_animation_and_state_change(struct session *ps, struct win *w, d
18831884
assert(false);
18841885
return true;
18851886
}
1887+
} else if (geometry_changed) {
1888+
assert(w->state == WSTATE_MAPPED);
1889+
trigger = ANIMATION_TRIGGER_GEOMETRY;
1890+
} else if (win_ctx.opacity_before != win_ctx.opacity) {
1891+
assert(w->state == WSTATE_MAPPED);
1892+
trigger = win_ctx.opacity > win_ctx.opacity_before
1893+
? ANIMATION_TRIGGER_INCREASE_OPACITY
1894+
: ANIMATION_TRIGGER_DECREASE_OPACITY;
18861895
}
18871896

1888-
if (trigger != ANIMATION_TRIGGER_INVALID && w->running_animation_instance &&
1889-
(w->running_animation.suppressions & (1 << trigger)) != 0) {
1897+
if (trigger == ANIMATION_TRIGGER_INVALID) {
1898+
// No state changes, if there's a animation running, we just continue it.
1899+
return win_advance_animation(w, delta_t, &win_ctx);
1900+
} else if (w->running_animation_instance &&
1901+
(w->running_animation.suppressions & (1 << trigger)) != 0) {
18901902
log_debug("Not starting animation %s for window %#010x (%s) because it "
18911903
"is being suppressed.",
18921904
animation_trigger_names[trigger], win_id(w), w->name);
@@ -1921,6 +1933,45 @@ bool win_process_animation_and_state_change(struct session *ps, struct win *w, d
19211933

19221934
auto new_animation = script_instance_new(wopts.animations[trigger].script);
19231935
if (w->running_animation_instance) {
1936+
if (geometry_changed) {
1937+
// If the window has moved, we need to adjust scripts
1938+
// outputs so that the window will stay in the same position and
1939+
// size after applying the animation. This way the window's size
1940+
// and position won't change discontinuously.
1941+
auto memory = w->running_animation_instance->memory;
1942+
auto output_indices = w->running_animation.output_indices;
1943+
struct {
1944+
int output;
1945+
double delta;
1946+
} adjustments[] = {
1947+
{WIN_SCRIPT_OFFSET_X, win_ctx.x_before - win_ctx.x},
1948+
{WIN_SCRIPT_OFFSET_Y, win_ctx.y_before - win_ctx.y},
1949+
{WIN_SCRIPT_SHADOW_OFFSET_X, win_ctx.x_before - win_ctx.x},
1950+
{WIN_SCRIPT_SHADOW_OFFSET_Y, win_ctx.y_before - win_ctx.y},
1951+
};
1952+
for (size_t i = 0; i < ARR_SIZE(adjustments); i++) {
1953+
if (output_indices[adjustments[i].output] >= 0) {
1954+
memory[output_indices[adjustments[i].output]] +=
1955+
adjustments[i].delta;
1956+
}
1957+
}
1958+
1959+
struct {
1960+
int output;
1961+
double factor;
1962+
} factors[] = {
1963+
{WIN_SCRIPT_SCALE_X, win_ctx.width_before / win_ctx.width},
1964+
{WIN_SCRIPT_SCALE_Y, win_ctx.height_before / win_ctx.height},
1965+
{WIN_SCRIPT_SHADOW_SCALE_X, win_ctx.width_before / win_ctx.width},
1966+
{WIN_SCRIPT_SHADOW_SCALE_Y, win_ctx.height_before / win_ctx.height},
1967+
};
1968+
for (size_t i = 0; i < ARR_SIZE(factors); i++) {
1969+
if (output_indices[factors[i].output] >= 0) {
1970+
memory[output_indices[factors[i].output]] *=
1971+
factors[i].factor;
1972+
}
1973+
}
1974+
}
19241975
script_instance_resume_from(w->running_animation_instance, new_animation);
19251976
free(w->running_animation_instance);
19261977
}

src/wm/win.h

+16-1
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,13 @@ struct win_geometry {
9090

9191
/// These are changes of window state that might trigger an animation. We separate them
9292
/// out and delay their application so determining which animation to run is easier.
93+
///
94+
/// These values are only hold for an instant, and once the animation is started they are
95+
/// updated to reflect the latest state.
9396
struct win_state_change {
9497
winstate_t state;
9598
double opacity;
99+
struct win_geometry g;
96100
};
97101

98102
struct win {
@@ -261,6 +265,7 @@ struct win {
261265

262266
struct win_script_context {
263267
double x, y, width, height;
268+
double x_before, y_before, width_before, height_before;
264269
double opacity_before, opacity;
265270
double monitor_x, monitor_y;
266271
double monitor_width, monitor_height;
@@ -273,6 +278,10 @@ static const struct script_context_info win_script_context_info[] = {
273278
{"window-y", offsetof(struct win_script_context, y)},
274279
{"window-width", offsetof(struct win_script_context, width)},
275280
{"window-height", offsetof(struct win_script_context, height)},
281+
{"window-x-before", offsetof(struct win_script_context, x_before)},
282+
{"window-y-before", offsetof(struct win_script_context, y_before)},
283+
{"window-width-before", offsetof(struct win_script_context, width_before)},
284+
{"window-height-before", offsetof(struct win_script_context, height_before)},
276285
{"window-raw-opacity-before", offsetof(struct win_script_context, opacity_before)},
277286
{"window-raw-opacity", offsetof(struct win_script_context, opacity)},
278287
{"window-monitor-x", offsetof(struct win_script_context, monitor_x)},
@@ -317,7 +326,7 @@ static const struct window_maybe_options WIN_MAYBE_OPTIONS_DEFAULT = {
317326

318327
static inline void win_script_fold(const struct win_script *upper,
319328
const struct win_script *lower, struct win_script *output) {
320-
for (size_t i = 0; i <= ANIMATION_TRIGGER_LAST; i++) {
329+
for (size_t i = 0; i < ANIMATION_TRIGGER_COUNT; i++) {
321330
output[i] = upper[i].script ? upper[i] : lower[i];
322331
}
323332
}
@@ -375,6 +384,12 @@ win_options(const struct win *w) {
375384
win_maybe_options_fold(w->options_override, w->options), *w->options_default);
376385
}
377386

387+
/// Check if win_geometry `a` and `b` have the same sizes and positions. Border width is
388+
/// not considered.
389+
static inline bool win_geometry_eq(struct win_geometry a, struct win_geometry b) {
390+
return a.x == b.x && a.y == b.y && a.width == b.width && a.height == b.height;
391+
}
392+
378393
/// Process pending updates/images flags on a window. Has to be called in X critical
379394
/// section. Returns true if the window had an animation running and it has just finished,
380395
/// or if the window's states just changed and there is no animation defined for this

0 commit comments

Comments
 (0)