From 4985fa178be59b53078009cdaec723a18282b412 Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Tue, 27 Aug 2024 06:25:15 -0600 Subject: [PATCH] animate: Add squeezimize animation (#2408) * animate: Add squeeimize animation * Fix clipping * Fix minimize to bottom * Improve squeezimize animation * Fix minimize to bottom (again) * Initialize last_direction variable This fixes disappearing windows on animation start after reversing the animation once. * Fixup object lifetime * Add options for squeezimize * Compute bounding box for animation damage * Port to shader * Fix downward squeezimize * Drop squeezimize line height option This optimization is no longer needed since we are using a fast fragment shader now. * Fix builtins * Omit unnecessary conditional in shader * Simplify animation initialization function * Fix squeezimize for shaded views * squeezimize: Update animation geometry each frame This makes it so it doesn't get the wrong clipping area when other transformations are applied. * Use more unique name for transformer name variable This will help avoid conflicts of the same variable name in scope. * squeezimize: Update according to suggestions in review * squeezimize: Drop damage pre_hook This is taken care of in transform_damage_region(). * squeezimize: Avoid storing view on transformer * squeezimize: Fix typo * animate: Set default squeezimize duration to 150ms * animate: Minimize to bottom center of output if target is not set --- metadata/animate.xml | 22 ++ plugins/animate/animate.cpp | 24 ++- plugins/animate/squeezimize.hpp | 371 ++++++++++++++++++++++++++++++++ 3 files changed, 415 insertions(+), 2 deletions(-) create mode 100644 plugins/animate/squeezimize.hpp diff --git a/metadata/animate.xml b/metadata/animate.xml index 31da5891e..385de9276 100644 --- a/metadata/animate.xml +++ b/metadata/animate.xml @@ -47,6 +47,23 @@ <_name>Fire + + diff --git a/plugins/animate/animate.cpp b/plugins/animate/animate.cpp index 8b7fb7805..a7a6bd285 100644 --- a/plugins/animate/animate.cpp +++ b/plugins/animate/animate.cpp @@ -8,6 +8,7 @@ #include "animate.hpp" #include "system_fade.hpp" #include "basic_animations.hpp" +#include "squeezimize.hpp" #include "fire/fire.hpp" #include "unmapped-view-node.hpp" #include "wayfire/plugin.hpp" @@ -266,6 +267,7 @@ class wayfire_animation : public wf::plugin_interface_t, private wf::per_output_ { wf::option_wrapper_t open_animation{"animate/open_animation"}; wf::option_wrapper_t close_animation{"animate/close_animation"}; + wf::option_wrapper_t minimize_animation{"animate/minimize_animation"}; wf::option_wrapper_t default_duration{"animate/duration"}; wf::option_wrapper_t fade_duration{"animate/fade_duration"}; @@ -444,10 +446,28 @@ class wayfire_animation : public wf::plugin_interface_t, private wf::per_output_ { if (ev->state) { - set_animation(ev->view, ANIMATION_TYPE_MINIMIZE, default_duration, "minimize"); + if (std::string(minimize_animation) == "squeezimize") + { + set_animation(ev->view, ANIMATION_TYPE_MINIMIZE, + default_duration, + "minimize"); + } else if (std::string(minimize_animation) == "zoom") + { + set_animation(ev->view, ANIMATION_TYPE_MINIMIZE, default_duration, + "minimize"); + } } else { - set_animation(ev->view, ANIMATION_TYPE_RESTORE, default_duration, "minimize"); + if (std::string(minimize_animation) == "squeezimize") + { + set_animation(ev->view, ANIMATION_TYPE_RESTORE, + default_duration, + "minimize"); + } else if (std::string(minimize_animation) == "zoom") + { + set_animation(ev->view, ANIMATION_TYPE_RESTORE, default_duration, + "minimize"); + } } // ev->carried_out should remain false, so that core also does the automatic minimize/restore and diff --git a/plugins/animate/squeezimize.hpp b/plugins/animate/squeezimize.hpp new file mode 100644 index 000000000..c60d829b4 --- /dev/null +++ b/plugins/animate/squeezimize.hpp @@ -0,0 +1,371 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 Scott Moreau + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "animate.hpp" + + +wf::option_wrapper_t squeezimize_duration{"animate/squeezimize_duration"}; + +static const char *squeeze_vert_source = + R"( +#version 100 + +attribute mediump vec2 position; +attribute mediump vec2 uv_in; + +uniform mat4 matrix; + +varying highp vec2 uv; + +void main() { + uv = uv_in; + gl_Position = matrix * vec4(position, 0.0, 1.0); +} +)"; + +static const char *squeeze_frag_source = + R"( +#version 100 +@builtin_ext@ +@builtin@ + +precision mediump float; + +varying highp vec2 uv; +uniform mediump float progress; +uniform mediump vec4 src_box; +uniform mediump vec4 target_box; +uniform int upward; + +void main() +{ + float y; + vec2 uv_squeeze; + float inv_w = 1.0 / (src_box.z - src_box.x); + float inv_h = 1.0 / (src_box.w - src_box.y); + float progress_pt_one = clamp(progress, 0.0, 0.5) * 2.0; + float progress_pt_two = (clamp(progress, 0.5, 1.0) - 0.5) * 2.0; + + uv_squeeze.x = (uv.x * inv_w) - (inv_w - 1.0); + uv_squeeze.x += inv_w - inv_w * src_box.z; + uv_squeeze.y = (uv.y * inv_h) - (inv_h - 1.0); + uv_squeeze.y += inv_h * src_box.y; + + if (upward == 1) + { + y = uv.y; + uv_squeeze.y += -progress_pt_two * (inv_h - target_box.w); + } else + { + y = 1.0 - uv.y; + uv_squeeze.y -= -progress_pt_two * (src_box.y + target_box.y + target_box.w); + } + + float sigmoid = 1.0 / (1.0 + pow(2.718, -((y * inv_h) * 6.0 - 3.0))); + sigmoid *= progress_pt_one * (src_box.x - target_box.x); + + uv_squeeze.x += sigmoid * inv_w; + uv_squeeze.x *= (y * (1.0 / (target_box.z - target_box.x)) * progress_pt_one) + 1.0; + + if (uv_squeeze.x < 0.0 || uv_squeeze.y < 0.0 || + uv_squeeze.x > 1.0 || uv_squeeze.y > 1.0) + { + discard; + } + + gl_FragColor = get_pixel(uv_squeeze); +} +)"; + +namespace wf +{ +namespace squeezimize +{ +static std::string squeezimize_transformer_name = "animation-squeezimize"; +using namespace wf::scene; +using namespace wf::animation; +class squeezimize_animation_t : public duration_t +{ + public: + using duration_t::duration_t; + timed_transition_t squeeze{*this}; +}; +class squeezimize_transformer : public wf::scene::view_2d_transformer_t +{ + public: + wf::output_t *output; + OpenGL::program_t program; + wf::geometry_t minimize_target; + wf::geometry_t animation_geometry; + squeezimize_animation_t progression{squeezimize_duration}; + + class simple_node_render_instance_t : public wf::scene::transformer_render_instance_t + { + wf::signal::connection_t on_node_damaged = + [=] (node_damage_signal *ev) + { + push_to_parent(ev->region); + }; + + damage_callback push_to_parent; + + public: + simple_node_render_instance_t(squeezimize_transformer *self, damage_callback push_damage, + wf::output_t *output) : wf::scene::transformer_render_instance_t(self, + push_damage, + output) + { + this->push_to_parent = push_damage; + self->connect(&on_node_damaged); + } + + ~simple_node_render_instance_t() + {} + + void schedule_instructions( + std::vector& instructions, + const wf::render_target_t& target, wf::region_t& damage) override + { + instructions.push_back(render_instruction_t{ + .instance = this, + .target = target, + .damage = damage & self->get_bounding_box(), + }); + } + + void transform_damage_region(wf::region_t& damage) override + { + damage |= wf::region_t{self->animation_geometry}; + } + + void render(const wf::render_target_t& target, + const wf::region_t& damage) override + { + auto src_box = self->get_children_bounding_box(); + auto src_tex = wf::scene::transformer_render_instance_t::get_texture( + 1.0); + auto progress = self->progression.progress(); + int upward = ((src_box.y > self->minimize_target.y) || + ((src_box.y < 0) && + (self->minimize_target.y < self->output->get_relative_geometry().height / 2))); + static const float vertex_data_uv[] = { + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f, + }; + + self->animation_geometry.x = std::min(src_box.x, self->minimize_target.x); + self->animation_geometry.y = std::min(src_box.y, self->minimize_target.y); + self->animation_geometry.width = + std::max(std::max(std::max(src_box.width, + self->minimize_target.width), + (self->minimize_target.x + self->minimize_target.width) - src_box.x), + (src_box.x + src_box.width) - self->minimize_target.x); + self->animation_geometry.height = + std::max(std::max(std::max(src_box.height, + self->minimize_target.height), + (self->minimize_target.y + self->minimize_target.height) - src_box.y), + (src_box.y + src_box.height) - self->minimize_target.y); + + const float vertex_data_pos[] = { + 1.0f * self->animation_geometry.x, + 1.0f * self->animation_geometry.y + self->animation_geometry.height, + 1.0f * self->animation_geometry.x + self->animation_geometry.width, + 1.0f * self->animation_geometry.y + self->animation_geometry.height, + 1.0f * self->animation_geometry.x + self->animation_geometry.width, + 1.0f * self->animation_geometry.y, + 1.0f * self->animation_geometry.x, 1.0f * self->animation_geometry.y, + }; + + const glm::vec4 src_box_pos{ + float(src_box.x - self->animation_geometry.x) / self->animation_geometry.width, + float(src_box.y - self->animation_geometry.y) / self->animation_geometry.height, + float((src_box.x - self->animation_geometry.x) + src_box.width) / + self->animation_geometry.width, + float((src_box.y - self->animation_geometry.y) + src_box.height) / + self->animation_geometry.height + }; + + const glm::vec4 target_box_pos{ + float(self->minimize_target.x - self->animation_geometry.x) / self->animation_geometry.width, + float(self->minimize_target.y - self->animation_geometry.y) / self->animation_geometry.height, + float((self->minimize_target.x - self->animation_geometry.x) + self->minimize_target.width) / + self->animation_geometry.width, + float((self->minimize_target.y - self->animation_geometry.y) + self->minimize_target.height) / + self->animation_geometry.height + }; + + OpenGL::render_begin(target); + self->program.use(wf::TEXTURE_TYPE_RGBA); + self->program.uniformMatrix4f("matrix", target.get_orthographic_projection()); + self->program.attrib_pointer("position", 2, 0, vertex_data_pos); + self->program.attrib_pointer("uv_in", 2, 0, vertex_data_uv); + self->program.uniform1i("upward", upward); + self->program.uniform1f("progress", progress); + self->program.uniform4f("src_box", src_box_pos); + self->program.uniform4f("target_box", target_box_pos); + self->program.set_active_texture(src_tex); + for (const auto& box : damage) + { + target.logic_scissor(wlr_box_from_pixman_box(box)); + GL_CALL(glDrawArrays(GL_TRIANGLE_FAN, 0, 4)); + } + + OpenGL::render_end(); + } + }; + + squeezimize_transformer(wayfire_view view, + wf::geometry_t minimize_target, wf::geometry_t bbox) : wf::scene::view_2d_transformer_t(view) + { + this->minimize_target = minimize_target; + /* If there is no minimize target set, minimize to the bottom center of the output */ + if ((this->minimize_target.width <= 0) || (this->minimize_target.height <= 0)) + { + if (auto output = view->get_output()) + { + auto og = output->get_relative_geometry(); + this->minimize_target.x = og.width / 2 - 50; + this->minimize_target.y = og.height; + this->minimize_target.width = 100; + this->minimize_target.height = 50; + } + } + + animation_geometry.x = std::min(bbox.x, this->minimize_target.x); + animation_geometry.y = std::min(bbox.y, this->minimize_target.y); + animation_geometry.width = + std::max(std::max(std::max(bbox.width, + this->minimize_target.width), + (this->minimize_target.x + this->minimize_target.width) - bbox.x), + (bbox.x + bbox.width) - this->minimize_target.x); + animation_geometry.height = + std::max(std::max(std::max(bbox.height, + this->minimize_target.height), + (this->minimize_target.y + this->minimize_target.height) - bbox.y), + (bbox.y + bbox.height) - this->minimize_target.y); + OpenGL::render_begin(); + program.compile(squeeze_vert_source, squeeze_frag_source); + OpenGL::render_end(); + } + + wf::geometry_t get_bounding_box() override + { + return this->animation_geometry; + } + + void gen_render_instances(std::vector& instances, + damage_callback push_damage, wf::output_t *shown_on) override + { + instances.push_back(std::make_unique( + this, push_damage, shown_on)); + } + + void init_animation(bool squeeze) + { + if (!squeeze) + { + this->progression.reverse(); + } + + this->progression.start(); + } + + virtual ~squeezimize_transformer() + { + program.free_resources(); + } +}; + +class squeezimize_animation : public animation_base +{ + wayfire_view view; + + public: + void init(wayfire_view view, wf::animation_description_t dur, wf_animation_type type) override + { + this->view = view; + pop_transformer(view); + auto bbox = view->get_transformed_node()->get_children_bounding_box(); + auto toplevel = wf::toplevel_cast(view); + wf::dassert(toplevel != nullptr, "We cannot minimize non-toplevel views!"); + auto hint = toplevel->get_minimize_hint(); + auto tmgr = view->get_transformed_node(); + auto node = std::make_shared(view, hint, bbox); + tmgr->add_transformer(node, wf::TRANSFORMER_HIGHLEVEL + 1, squeezimize_transformer_name); + node->init_animation(type & HIDING_ANIMATION); + } + + void pop_transformer(wayfire_view view) + { + view->get_transformed_node()->rem_transformer(squeezimize_transformer_name); + } + + bool step() override + { + auto tmgr = view->get_transformed_node(); + + if (auto tr = + tmgr->get_transformer(squeezimize_transformer_name)) + { + auto running = tr->progression.running(); + if (!running) + { + pop_transformer(view); + return false; + } + + return running; + } + + return false; + } + + void reverse() override + { + if (auto tr = + view->get_transformed_node()->get_transformer( + squeezimize_transformer_name)) + { + tr->progression.reverse(); + } + } +}; +} +}