feature: workspace switching animations (#128)

This commit is contained in:
Matthew Kosarek 2024-05-15 11:40:40 -04:00 committed by GitHub
parent 6789815eaf
commit a1da899ded
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 270 additions and 95 deletions

View File

@ -29,6 +29,10 @@ AnimateableEvent miracle::from_string_animateable_event(std::string const& str)
return AnimateableEvent::window_move;
else if (str == "window_close")
return AnimateableEvent::window_close;
else if (str == "window_workspace_show")
return AnimateableEvent::window_workspace_show;
else if (str == "window_workspace_hide")
return AnimateableEvent::window_workspace_hide;
else
{
mir::log_error("from_string_animateable_eventfrom_string: unknown string: %s", str.c_str());
@ -122,4 +126,4 @@ AnimationType miracle::from_string_animation_type(std::string const& str)
mir::log_error("from_string_animation_type: unknown string: %s", str.c_str());
return AnimationType::max;
}
}
}

View File

@ -18,7 +18,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef MIRACLE_WM_ANIMATION_DEFINTION_H
#define MIRACLE_WM_ANIMATION_DEFINTION_H
#include "direction.h"
#include "mir/geometry/point.h"
#include <optional>
#include <string>
namespace miracle
@ -29,6 +30,8 @@ enum class AnimateableEvent
window_open,
window_move,
window_close,
window_workspace_show,
window_workspace_hide,
max
};
@ -94,6 +97,10 @@ struct AnimationDefinition
float c5 = 1.3962634015954636;
float n1 = 7.5625;
float d1 = 2.75;
// Slide-specific values
std::optional<mir::geometry::Point> slide_to;
std::optional<mir::geometry::Point> slide_from;
};
AnimateableEvent from_string_animateable_event(std::string const&);

View File

@ -196,6 +196,16 @@ AnimationStepResult Animation::init()
{},
glm::mat4(0.f)
};
case AnimationType::slide:
return {
handle,
false,
from.has_value()
? std::optional<glm::vec2>(glm::vec2(from.value().top_left.x.as_int(), from.value().top_left.y.as_int()))
: std::nullopt,
{},
{}
};
default:
return {
handle,
@ -215,8 +225,8 @@ AnimationStepResult Animation::step()
return {
handle,
true,
glm::vec2(to.top_left.x.as_int(), to.top_left.y.as_int()),
glm::vec2(to.size.width.as_int(), to.size.height.as_int()),
!to.has_value() ? std::nullopt : std::optional<glm::vec2>(glm::vec2(to.value().top_left.x.as_int(), to.value().top_left.y.as_int())),
!to.has_value() ? std::nullopt : std::optional<glm::vec2>(glm::vec2(to.value().size.width.as_int(), to.value().size.height.as_int())),
glm::mat4(1.f),
};
}
@ -227,20 +237,20 @@ AnimationStepResult Animation::step()
case AnimationType::slide:
{
auto p = ease(definition, t);
auto distance = to.top_left - from.top_left;
auto distance = to.value().top_left - from.value().top_left;
float x = (float)distance.dx.as_int() * p;
float y = (float)distance.dy.as_int() * p;
glm::vec2 position = {
(float)from.top_left.x.as_int() + x,
(float)from.top_left.y.as_int() + y
(float)from.value().top_left.x.as_int() + x,
(float)from.value().top_left.y.as_int() + y
};
return {
handle,
false,
position,
glm::vec2(to.size.width.as_int(), to.size.height.as_int()),
glm::vec2(to.value().size.width.as_int(), to.value().size.height.as_int()),
std::nullopt
};
}
@ -345,6 +355,57 @@ void Animator::window_open(
cv.notify_one();
}
void Animator::workspace_move_to(
AnimationHandle handle,
int x_offset,
std::function<void(AnimationStepResult const&)> const& from_callback,
std::function<void(AnimationStepResult const&)> const& to_callback)
{
if (!config->are_animations_enabled())
{
from_callback({ handle, true });
to_callback({ handle, true });
return;
}
mir::geometry::Rectangle from_start(
mir::geometry::Point{0, 0},
mir::geometry::Size{0, 0}
);
mir::geometry::Rectangle from_end(
mir::geometry::Point{-x_offset, 0},
mir::geometry::Size{0, 0}
);
Animation from_animation = Animation::window_move(handle,
config->get_animation_definitions()[(int)AnimateableEvent::window_workspace_hide],
from_start,
from_end,
from_callback);
mir::geometry::Rectangle to_start(
mir::geometry::Point{x_offset, 0},
mir::geometry::Size{0, 0}
);
mir::geometry::Rectangle to_end(
mir::geometry::Point{0, 0},
mir::geometry::Size{0, 0}
);
Animation to_animation = Animation::window_move(handle,
config->get_animation_definitions()[(int)AnimateableEvent::window_workspace_hide],
to_start,
to_end,
to_callback);
from_callback(from_animation.init());
to_callback(to_animation.init());
std::lock_guard<std::mutex> lock(processing_lock);
queued_animations.push_back(std::move(from_animation));
queued_animations.push_back(std::move(to_animation));
cv.notify_one();
}
namespace
{
struct PendingUpdateData
@ -417,4 +478,4 @@ void Animator::stop()
running = false;
run_thread.join();
}
}

View File

@ -75,8 +75,8 @@ public:
private:
AnimationHandle handle;
AnimationDefinition definition;
mir::geometry::Rectangle from;
mir::geometry::Rectangle to;
std::optional<mir::geometry::Rectangle> from;
std::optional<mir::geometry::Rectangle> to;
const float timestep_seconds = 0.016;
std::function<void(AnimationStepResult const&)> callback;
float runtime_seconds = 0.f;
@ -106,6 +106,12 @@ public:
AnimationHandle handle,
std::function<void(AnimationStepResult const&)> const& callback);
void workspace_move_to(
AnimationHandle handle,
int x_offset, // The offset in X from which the "to" callback transform begins if it is a slide
std::function<void(AnimationStepResult const&)> const& from_callback,
std::function<void(AnimationStepResult const&)> const& to_callback);
void stop();
private:

View File

@ -85,9 +85,6 @@ glm::vec4 parse_color(YAML::Node const& node)
std::string wrap_command(std::string const& command)
{
if (std::getenv("SNAP"))
return "miracle-wm-unsnap " + command;
return command;
}
@ -799,6 +796,16 @@ void MiracleConfig::read_animation_definitions(YAML::Node const& root)
AnimationType::shrink,
EaseFunction::ease_out_back,
0.25f,
},
{
AnimationType::slide,
EaseFunction::ease_in_out_elastic,
0.5f
},
{
AnimationType::slide,
EaseFunction::ease_in_out_elastic,
0.5f
}
});
if (root["animations"])
@ -1081,4 +1088,4 @@ std::array<AnimationDefinition, (int)AnimateableEvent::max> const& MiracleConfig
bool MiracleConfig::are_animations_enabled() const
{
return animations_enabled;
}
}

View File

@ -15,15 +15,21 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
**/
#include "window_metadata.h"
#include "workspace_content.h"
#include <memory>
#define MIR_LOG_COMPONENT "output_content"
#include "output_content.h"
#include "animator.h"
#include "leaf_node.h"
#include "output_content.h"
#include "window_helpers.h"
#include "workspace_manager.h"
#include <mir/log.h>
#include <mir/scene/surface.h>
#include <miral/toolkit_event.h>
#include <miral/window_info.h>
#include <glm/gtx/transform.hpp>
using namespace miracle;
@ -34,14 +40,17 @@ OutputContent::OutputContent(
miral::WindowManagerTools const& tools,
miral::MinimalWindowManager& floating_window_manager,
std::shared_ptr<MiracleConfig> const& config,
TilingInterface& node_interface) :
TilingInterface& node_interface,
Animator& animator) :
output { output },
workspace_manager { workspace_manager },
area { area },
tools { tools },
floating_window_manager { floating_window_manager },
config { config },
node_interface { node_interface }
node_interface { node_interface },
animator { animator },
animation_handle { animator.register_animateable() }
{
}
@ -402,41 +411,93 @@ void OutputContent::advise_workspace_deleted(int workspace)
bool OutputContent::advise_workspace_active(int key)
{
std::shared_ptr<WorkspaceContent> to = nullptr;
std::shared_ptr<WorkspaceContent> from = nullptr;
for (auto& workspace : workspaces)
{
if (workspace->get_workspace() == active_workspace)
from = workspace;
if (workspace->get_workspace() == key)
{
if (active_workspace == key)
return true;
std::shared_ptr<WorkspaceContent> previous_workspace = nullptr;
std::vector<std::shared_ptr<WindowMetadata>> pinned_windows;
for (auto& other : workspaces)
{
if (other->get_workspace() == active_workspace)
{
previous_workspace = other;
pinned_windows = other->hide();
break;
}
}
active_workspace = key;
workspace->show(pinned_windows);
// Important: Delete the workspace only after we have shown the new one because we may want
// to move a node to the new workspace.
if (previous_workspace != nullptr)
{
auto active_tree = previous_workspace->get_tree();
if (active_tree->is_empty() && previous_workspace->get_floating_windows().empty())
workspace_manager.delete_workspace(previous_workspace->get_workspace());
}
return true;
to = workspace;
}
}
return false;
// TODO: Handle pinned windows
// TODO This is an abuse of the sliding animation system, but it at least proves a point. "Slide"
// means different things in different contexts, so it seems.
auto travel_distance = active_workspace > key ? (-area.size.width.as_int()) : area.size.width.as_int();
animator.workspace_move_to(animation_handle,
travel_distance,
[from = from, this](AnimationStepResult const& asr)
{
if (!from)
return;
if (asr.is_complete)
{
from->hide();
return;
}
from->for_each_window([&](std::shared_ptr<WindowMetadata> const& metadata)
{
if (metadata->get_is_pinned())
return;
auto& window = metadata->get_window();
AnimationStepResult for_window = asr;
if (for_window.position)
{
for_window.transform = glm::translate(
for_window.transform ? for_window.transform.value() : glm::mat4(1.f),
glm::vec3(asr.position.value().x, asr.position.value().y, 0));
for_window.position = std::nullopt;
for_window.size = std::nullopt;
}
node_interface.on_animation(for_window, metadata);
});
},
[to=to,from=from, this](AnimationStepResult const& asr)
{
to->for_each_window([&](std::shared_ptr<WindowMetadata> const& metadata)
{
if (metadata->get_is_pinned())
return;
auto& window = metadata->get_window();
AnimationStepResult for_window = asr;
if (for_window.position)
{
for_window.transform = glm::translate(
for_window.transform ? for_window.transform.value() : glm::mat4(1.f),
glm::vec3(asr.position.value().x, asr.position.value().y, 0));
for_window.position = std::nullopt;
for_window.size = std::nullopt;
}
node_interface.on_animation(for_window, metadata);
});
});
to->show({});
active_workspace = key;
// Important: Delete the workspace only after we have shown the new one because we may want
// to move a node to the new workspace.
if (from != nullptr)
{
auto active_tree = from->get_tree();
if (active_tree->is_empty() && from->get_floating_windows().empty())
workspace_manager.delete_workspace(from->get_workspace());
}
return true;
}
void OutputContent::advise_application_zone_create(miral::Zone const& application_zone)

View File

@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef MIRACLE_SCREEN_H
#define MIRACLE_SCREEN_H
#include "animator.h"
#include "miral/window.h"
#include "tiling_window_tree.h"
#include "window_metadata.h"
@ -31,6 +32,7 @@ namespace miracle
class WorkspaceManager;
class MiracleConfig;
class WindowManagerToolsTilingInterface;
class Animator;
class OutputContent
{
@ -42,7 +44,8 @@ public:
miral::WindowManagerTools const& tools,
miral::MinimalWindowManager& floating_window_manager,
std::shared_ptr<MiracleConfig> const& options,
TilingInterface&);
TilingInterface&,
Animator&);
~OutputContent() = default;
[[nodiscard]] std::shared_ptr<TilingWindowTree> get_active_tree() const;
@ -113,11 +116,13 @@ private:
geom::Rectangle area;
std::shared_ptr<MiracleConfig> config;
TilingInterface& node_interface;
Animator& animator;
int active_workspace = -1;
std::vector<std::shared_ptr<WorkspaceContent>> workspaces;
std::vector<miral::Zone> application_zone_list;
bool is_active_ = false;
miral::Window active_window;
AnimationHandle animation_handle;
};
}

View File

@ -423,7 +423,7 @@ void Policy::advise_output_create(miral::Output const& output)
{
auto new_tree = std::make_shared<OutputContent>(
output, workspace_manager, output.extents(), window_manager_tools,
floating_window_manager, config, node_interface);
floating_window_manager, config, node_interface, animator);
workspace_manager.request_first_available_workspace(new_tree);
output_list.push_back(new_tree);
if (active_output == nullptr)

View File

@ -26,6 +26,7 @@ namespace miracle
{
class WindowMetadata;
class TilingWindowTree;
class AnimationStepResult;
class TilingInterface
{
@ -42,6 +43,7 @@ public:
virtual void raise(miral::Window const&) = 0;
virtual void send_to_back(miral::Window const&) = 0;
virtual void open(miral::Window const&) = 0;
virtual void on_animation(miracle::AnimationStepResult const& result, std::shared_ptr<WindowMetadata> const&) = 0;
};
}

View File

@ -46,21 +46,9 @@ void WindowManagerToolsTilingInterface::open(miral::Window const& window)
animator.window_open(
metadata->get_animation_handle(),
[this, window = window](miracle::AnimationStepResult const& result)
[this, metadata=metadata](miracle::AnimationStepResult const& result)
{
if (!result.transform)
return;
auto surface = window.operator std::shared_ptr<mir::scene::Surface>();
if (!surface)
return;
surface->set_transformation(result.transform.value());
if (result.is_complete)
clip(window, { window.top_left(), window.size() });
else
noclip(window);
on_animation(result, metadata);
});
}
@ -86,41 +74,7 @@ void WindowManagerToolsTilingInterface::set_rectangle(
r,
[this, metadata = metadata](miracle::AnimationStepResult const& result)
{
auto window = metadata->get_window();
auto surface = window.operator std::shared_ptr<mir::scene::Surface>();
if (!surface)
return;
if (result.transform)
surface->set_transformation(result.transform.value());
miral::WindowSpecification spec;
auto top_left = result.position
? mir::geometry::Point(
result.position.value().x,
result.position.value().y)
: window.top_left();
auto size = result.size
? mir::geometry::Size(
result.size.value().x,
result.size.value().y)
: window.size();
spec.top_left() = top_left;
spec.size() = size;
tools.modify_window(window, spec);
auto& window_info = tools.info_for(window);
mir::geometry::Rectangle new_rectangle(top_left, size);
clip(window, new_rectangle);
for (auto const& child : window_info.children())
{
miral::WindowSpecification sub_spec;
sub_spec.top_left() = spec.top_left();
sub_spec.size() = spec.size();
tools.modify_window(child, sub_spec);
}
on_animation(result, metadata);
});
}
@ -186,4 +140,48 @@ void WindowManagerToolsTilingInterface::raise(miral::Window const& window)
void WindowManagerToolsTilingInterface::send_to_back(miral::Window const& window)
{
tools.send_tree_to_back(window);
}
void WindowManagerToolsTilingInterface::on_animation(
miracle::AnimationStepResult const& result, std::shared_ptr<WindowMetadata> const& metadata)
{
auto window = metadata->get_window();
auto surface = window.operator std::shared_ptr<mir::scene::Surface>();
if (!surface)
return;
if (result.transform)
surface->set_transformation(result.transform.value());
miral::WindowSpecification spec;
auto top_left = result.position
? mir::geometry::Point(
result.position.value().x,
result.position.value().y)
: window.top_left();
auto size = result.size
? mir::geometry::Size(
result.size.value().x,
result.size.value().y)
: window.size();
spec.top_left() = top_left;
spec.size() = size;
tools.modify_window(window, spec);
auto& window_info = tools.info_for(window);
mir::geometry::Rectangle new_rectangle(top_left, size);
if (result.is_complete)
clip(window, new_rectangle);
else
noclip(window);
for (auto const& child : window_info.children())
{
miral::WindowSpecification sub_spec;
sub_spec.top_left() = spec.top_left();
sub_spec.size() = spec.size();
tools.modify_window(child, sub_spec);
}
}

View File

@ -41,6 +41,7 @@ public:
std::shared_ptr<WindowMetadata> get_metadata(miral::Window const&, TilingWindowTree const*) override;
void raise(miral::Window const&) override;
void send_to_back(miral::Window const&) override;
void on_animation(miracle::AnimationStepResult const& result, std::shared_ptr<WindowMetadata> const&) override;
private:
miral::WindowManagerTools tools;

View File

@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define MIR_LOG_COMPONENT "workspace_content"
#include "workspace_content.h"
#include "leaf_node.h"
#include "tiling_window_tree.h"
#include "window_helpers.h"
#include "window_metadata.h"
@ -71,6 +72,26 @@ void WorkspaceContent::show(std::vector<std::shared_ptr<WindowMetadata>> const&
}
}
void WorkspaceContent::for_each_window(std::function<void(std::shared_ptr<WindowMetadata>)> const& f)
{
for (auto const& window : floating_windows)
{
auto metadata = window_helpers::get_metadata(window, tools);
if (metadata)
f(metadata);
}
tree->foreach_node([&](std::shared_ptr<Node> const& node)
{
if (auto leaf = Node::as_leaf(node))
{
auto metadata = window_helpers::get_metadata(leaf->get_window(), tools);
if (metadata)
f(metadata);
}
});
}
std::vector<std::shared_ptr<WindowMetadata>> WorkspaceContent::hide()
{
tree->hide();
@ -126,4 +147,4 @@ void WorkspaceContent::add_floating_window(miral::Window const& window)
void WorkspaceContent::remove_floating_window(miral::Window const& window)
{
floating_windows.erase(std::remove(floating_windows.begin(), floating_windows.end(), window));
}
}

View File

@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef MIRACLEWM_WORKSPACE_CONTENT_H
#define MIRACLEWM_WORKSPACE_CONTENT_H
#include <memory>
#include <miral/minimal_window_manager.h>
#include <miral/window_manager_tools.h>
@ -43,6 +44,7 @@ public:
[[nodiscard]] std::shared_ptr<TilingWindowTree> get_tree() const;
void show(std::vector<std::shared_ptr<WindowMetadata>> const&);
std::vector<std::shared_ptr<WindowMetadata>> hide();
void for_each_window(std::function<void(std::shared_ptr<WindowMetadata>)> const&);
bool has_floating_window(miral::Window const&);
void add_floating_window(miral::Window const&);