feature: animation groundwork + window movement animation (#121)

This commit is contained in:
Matthew Kosarek 2024-05-08 07:55:39 -04:00 committed by GitHub
parent 57515dda03
commit 66ce46ee07
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 392 additions and 52 deletions

View File

@ -37,27 +37,6 @@ AlignConsecutiveShortCaseStatements:
AcrossEmptyLines: false
AcrossComments: false
AlignCaseColons: false
AlignConsecutiveTableGenBreakingDAGArgColons:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveTableGenCondOperatorColons:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveTableGenDefinitionColons:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignEscapedNewlines: Right
AlignOperands: DontAlign
AlignTrailingComments:
@ -84,7 +63,6 @@ BitFieldColonSpacing: Both
BreakAdjacentStringLiterals: true
BreakAfterAttributes: Leave
BreakAfterJavaFieldAnnotations: false
BreakAfterReturnType: None
BreakArrays: true
BreakBeforeBinaryOperators: All
BreakBeforeConceptDeclarations: Always
@ -92,10 +70,8 @@ BreakBeforeBraces: Allman
BreakBeforeInlineASMColon: OnlyMultiline
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: AfterColon
BreakFunctionDefinitionParameters: false
BreakInheritanceList: BeforeColon
BreakStringLiterals: true
BreakTemplateDeclarations: MultiLine
ColumnLimit: 0
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
@ -157,7 +133,6 @@ LambdaBodyIndentation: OuterScope
LineEnding: DeriveLF
MacroBlockBegin: ''
MacroBlockEnd: ''
MainIncludeChar: Quote
MaxEmptyLinesToKeep: 1
NamespaceIndentation: Inner
ObjCBinPackProtocolList: Auto
@ -171,7 +146,6 @@ PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakOpenParenthesis: 0
PenaltyBreakScopeResolution: 500
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
@ -189,7 +163,6 @@ RequiresClausePosition: OwnLine
RequiresExpressionIndentation: OuterScope
SeparateDefinitionBlocks: Leave
ShortNamespaceLines: 1
SkipMacroDefinitionBody: false
SortIncludes: CaseSensitive
SortJavaStaticImport: Before
SortUsingDeclarations: LexicographicNumeric
@ -237,7 +210,6 @@ StatementAttributeLikeMacros:
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TableGenBreakInsideDAGArg: DontBreak
TabWidth: 8
UseTab: Never
VerilogBreakBetweenInstancePorts: true

View File

@ -61,6 +61,7 @@ add_library(miracle-wm-implementation
src/i3_command_executor.cpp
src/surface_tracker.cpp
src/window_tools_accessor.cpp
src/animator.cpp
)
add_executable(miracle-wm

220
src/animator.cpp Normal file
View File

@ -0,0 +1,220 @@
/**
Copyright (C) 2024 Matthew Kosarek
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
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 "animator.h"
#include <mir/scene/surface.h>
#include <mir/server_action_queue.h>
#include <chrono>
#define MIR_LOG_COMPONENT "animator"
#include <mir/log.h>
using namespace miracle;
using namespace std::chrono_literals;
Animation::Animation(
miral::Window const& window,
miracle::AnimationType animation_type,
std::function<void(AnimationStepResult const&)> const& callback)
: window{window},
type{animation_type},
callback{callback}
{
}
Animation& Animation::operator=(miracle::Animation const& other)
{
window = other.window;
type = other.type;
from = other.from;
to = other.to;
endtime_seconds = other.endtime_seconds;
runtime_seconds = other.runtime_seconds;
return *this;
}
Animation Animation::move_lerp(
miral::Window const& window,
mir::geometry::Rectangle const& _from,
mir::geometry::Rectangle const& _to,
std::function<void(AnimationStepResult const&)> const& callback)
{
Animation result(window, AnimationType::move_lerp, callback);
result.from = _from;
result.to = _to;
result.endtime_seconds = 0.25f;
return result;
}
std::weak_ptr<mir::scene::Surface> Animation::get_surface() const
{
return window.operator std::weak_ptr<mir::scene::Surface>();
}
AnimationStepResult Animation::step()
{
auto weak_surface = get_surface();
if (weak_surface.expired())
{
return {
window,
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()),
glm::mat4(1.f),
};
}
runtime_seconds += timestep_seconds;
if (runtime_seconds >= endtime_seconds)
{
return {
window,
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()),
glm::mat4(1.f),
};
}
switch (type)
{
case AnimationType::move_lerp:
{
// First, we begin lerping the position
auto distance = to.top_left - from.top_left;
float fraction = (runtime_seconds / endtime_seconds);
float x = (float)distance.dx.as_int() * fraction;
float y = (float)distance.dy.as_int() * fraction;
glm::vec2 position = {
from.top_left.x.as_int() + x,
from.top_left.y.as_int() + y
};
return {
window,
false,
position,
glm::vec2(to.size.width.as_int(), to.size.height.as_int()),
glm::mat4(1.f)
};
}
default:
return {
window,
false,
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()),
glm::mat4(1.f)
};
}
}
Animator::Animator(
std::shared_ptr<mir::ServerActionQueue> const& server_action_queue)
: server_action_queue{server_action_queue},
run_thread([&]() { run(); })
{
}
Animator::~Animator()
{
};
void Animator::animate_window_movement(
miral::Window const& window,
mir::geometry::Rectangle const& from,
mir::geometry::Rectangle const& to,
std::function<void(AnimationStepResult const&)> const& callback)
{
std::lock_guard<std::mutex> lock(processing_lock);
for (auto it = queued_animations.begin(); it != queued_animations.end(); it++)
{
if (it->get_window() == window)
{
queued_animations.erase(it);
break;
}
}
queued_animations.push_back(Animation::move_lerp(
window,
from,
to,
callback));
cv.notify_one();
}
namespace
{
struct PendingUpdateData
{
AnimationStepResult result;
std::function<void(miracle::AnimationStepResult const&)> callback;
};
}
void Animator::run()
{
// https://gist.github.com/mariobadr/673bbd5545242fcf9482
using clock = std::chrono::high_resolution_clock;
constexpr std::chrono::nanoseconds timestep(16ms);
std::chrono::nanoseconds lag(0ns);
auto time_start = clock::now();
bool running = true;
while (running)
{
{
std::unique_lock lock(processing_lock);
if (queued_animations.empty())
{
cv.wait(lock);
time_start = clock::now();
}
}
auto delta_time = clock::now() - time_start;
time_start = clock::now();
lag += std::chrono::duration_cast<std::chrono::nanoseconds>(delta_time);
while(lag >= timestep) {
lag -= timestep;
std::vector<PendingUpdateData> update_data;
{
std::lock_guard<std::mutex> lock(processing_lock);
for (auto it = queued_animations.begin(); it != queued_animations.end();)
{
auto& item = *it;
auto result = item.step();
update_data.push_back({ result, item.get_callback() });
if (result.should_erase)
it = queued_animations.erase(it);
else
it++;
}
}
server_action_queue->enqueue(this, [&, update_data]() {
for (auto const& update_item : update_data)
update_item.callback(update_item.result);
});
}
}
}

109
src/animator.h Normal file
View File

@ -0,0 +1,109 @@
/**
Copyright (C) 2024 Matthew Kosarek
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
**/
#ifndef MIRACLEWM_ANIMATOR_H
#define MIRACLEWM_ANIMATOR_H
#include <miral/window.h>
#include <thread>
#include <mutex>
#include <glm/glm.hpp>
#include <condition_variable>
#include <functional>
#include <mir/geometry/rectangle.h>
namespace mir
{
class ServerActionQueue;
}
namespace miracle
{
enum class AnimationType
{
move_lerp
};
struct AnimationStepResult
{
miral::Window window;
bool should_erase = false;
glm::vec2 position;
glm::vec2 size;
glm::mat4 transform;
};
class Animation
{
public:
static Animation move_lerp(
miral::Window const& window,
mir::geometry::Rectangle const& from,
mir::geometry::Rectangle const& to,
std::function<void(AnimationStepResult const&)> const& callback);
Animation& operator=(Animation const& other);
AnimationStepResult step();
miral::Window const& get_window() const { return window; }
std::weak_ptr<mir::scene::Surface> get_surface() const;
std::function<void(AnimationStepResult const&)> get_callback() const { return callback; }
private:
Animation(
miral::Window const& window,
AnimationType animation_type,
std::function<void(AnimationStepResult const&)> const& callback);
miral::Window window;
AnimationType type;
mir::geometry::Rectangle from;
mir::geometry::Rectangle to;
float endtime_seconds = 1.f;
float runtime_seconds = 0.f;
const float timestep_seconds = 0.016;
std::function<void(AnimationStepResult const&)> callback;
};
class Animator
{
public:
/// This will take the MainLoop to schedule animation.
explicit Animator(std::shared_ptr<mir::ServerActionQueue> const&);
~Animator();
/// Queue an animation on a window from the provided point to the other
/// point. It is assumed that the window has already been moved to the
/// "to" position.
void animate_window_movement(
miral::Window const&,
mir::geometry::Rectangle const& from,
mir::geometry::Rectangle const& to,
std::function<void(AnimationStepResult const&)> const& callback);
private:
void run();
std::shared_ptr<mir::ServerActionQueue> server_action_queue;
std::vector<Animation> queued_animations;
std::thread run_thread;
std::mutex processing_lock;
std::condition_variable cv;
};
} // miracle
#endif // MIRACLEWM_ANIMATOR_H

View File

@ -37,7 +37,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <miral/window_management_options.h>
#include <miral/x11_support.h>
#include <miroil/open_gl_context.h>
#include <stdlib.h>
using namespace miral;

View File

@ -63,7 +63,8 @@ Policy::Policy(
i3_command_executor(*this, workspace_manager, tools),
surface_tracker{surface_tracker},
ipc { std::make_shared<Ipc>(runner, workspace_manager, *this, server.the_main_loop(), i3_command_executor) },
node_interface(tools)
animator(server.the_main_loop()),
node_interface(tools, animator)
{
workspace_observer_registrar.register_interest(ipc);

View File

@ -26,6 +26,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "window_metadata.h"
#include "workspace_manager.h"
#include "surface_tracker.h"
#include "animator.h"
#include <memory>
#include <miral/external_client.h>
@ -113,6 +114,7 @@ private:
WorkspaceObserverRegistrar workspace_observer_registrar;
WorkspaceManager workspace_manager;
std::shared_ptr<Ipc> ipc;
Animator animator;
WindowManagerToolsTilingInterface node_interface;
I3CommandExecutor i3_command_executor;
SurfaceTracker& surface_tracker;

View File

@ -521,10 +521,10 @@ void Renderer::draw(mg::Renderable const& renderable, OutlineContext* context) c
if (clip_area)
{
glEnable(GL_SCISSOR_TEST);
auto clip_x = clip_area.value().top_left.x.as_int();
glm::vec4 transformed_position = renderable.transformation() * glm::vec4(clip_area.value().top_left.x.as_int(), clip_area.value().top_left.y.as_int(), 0, 1);
// The Y-coordinate is always relative to the top, so we make it relative to the bottom.
auto clip_y = viewport.top_left.y.as_int() + viewport.size.height.as_int() - clip_area.value().top_left.y.as_int() - clip_area.value().size.height.as_int();
glm::vec4 clip_pos(clip_x, clip_y, 0, 1);
auto clip_y = viewport.top_left.y.as_int() + viewport.size.height.as_int() - transformed_position.y - clip_area.value().size.height.as_int();
glm::vec4 clip_pos(transformed_position.x, clip_y, 0, 1);
clip_pos = display_transform * clip_pos;
glScissor(

View File

@ -664,7 +664,7 @@ std::tuple<std::shared_ptr<ParentNode>, std::shared_ptr<ParentNode>> TilingWindo
auto to_update = handle_remove(node);
// Note: When we remove moving_node from its initial position, there's a chance
// that the target_lane was melted into another lane. Hence, we need to update it
// that the target_lane was melted into another lane. Hence, we need to run it
auto target_parent = to->get_parent().lock();
auto index = target_parent->get_index_of_node(to);
target_parent->graft_existing(node, index + 1);

View File

@ -19,12 +19,16 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "leaf_node.h"
#include "window_helpers.h"
#include "window_metadata.h"
#include "animator.h"
#include <mir/scene/surface.h>
using namespace miracle;
WindowManagerToolsTilingInterface::WindowManagerToolsTilingInterface(
miral::WindowManagerTools const& tools) :
tools { tools }
miral::WindowManagerTools const& tools,
Animator& animator) :
tools { tools },
animator { animator }
{
}
@ -34,21 +38,50 @@ bool WindowManagerToolsTilingInterface::is_fullscreen(miral::Window const& windo
return window_helpers::is_window_fullscreen(info.state());
}
void WindowManagerToolsTilingInterface::set_rectangle(miral::Window const& window, geom::Rectangle const& r)
void WindowManagerToolsTilingInterface::set_rectangle(
miral::Window const& window, geom::Rectangle const& r)
{
miral::WindowSpecification spec;
spec.top_left() = r.top_left;
spec.size() = r.size;
tools.modify_window(window, spec);
animator.animate_window_movement(
window,
geom::Rectangle(window.top_left(), window.size()),
r,
[this](miracle::AnimationStepResult const& result)
{
if (auto surface = result.window.operator std::shared_ptr<mir::scene::Surface>())
{
surface->set_transformation(result.transform);
// Set the positions on the windows and the sub windows
miral::WindowSpecification spec;
spec.top_left() = mir::geometry::Point(
result.position.x,
result.position.y
);
spec.size() = mir::geometry::Size(
result.size.x,
result.size.y
);
tools.modify_window(result.window, spec);
auto& window_info = tools.info_for(window);
for (auto const& child : window_info.children())
{
miral::WindowSpecification sub_spec;
sub_spec.top_left() = r.top_left;
sub_spec.size() = r.size;
tools.modify_window(child, sub_spec);
}
auto& window_info = tools.info_for(result.window);
mir::geometry::Rectangle new_rectangle(
mir::geometry::Point(
result.position.x,
result.position.y),
mir::geometry::Size(
result.size.x,
result.size.y));
clip(result.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);
}
}
}
);
}
MirWindowState WindowManagerToolsTilingInterface::get_state(miral::Window const& window)
@ -68,8 +101,8 @@ void WindowManagerToolsTilingInterface::change_state(miral::Window const& window
void WindowManagerToolsTilingInterface::clip(miral::Window const& window, geom::Rectangle const& r)
{
auto& window_info = tools.info_for(window);
window_info.clip_area(r);
// auto& window_info = tools.info_for(window);
// window_info.clip_area(r);
}
void WindowManagerToolsTilingInterface::noclip(miral::Window const& window)

View File

@ -23,10 +23,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
namespace miracle
{
class Animator;
class WindowManagerToolsTilingInterface : public TilingInterface
{
public:
explicit WindowManagerToolsTilingInterface(miral::WindowManagerTools const&);
WindowManagerToolsTilingInterface(miral::WindowManagerTools const&, Animator& animator);
bool is_fullscreen(miral::Window const&) override;
void set_rectangle(miral::Window const&, geom::Rectangle const&) override;
MirWindowState get_state(miral::Window const&) override;
@ -41,6 +43,7 @@ public:
private:
miral::WindowManagerTools tools;
Animator& animator;
};
}