diff --git a/.clang-format b/.clang-format
index 7707b89..98eb630 100644
--- a/.clang-format
+++ b/.clang-format
@@ -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
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e01d3f6..8ea4d61 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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
diff --git a/src/animator.cpp b/src/animator.cpp
new file mode 100644
index 0000000..16c0c53
--- /dev/null
+++ b/src/animator.cpp
@@ -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 .
+**/
+
+#include "animator.h"
+#include
+#include
+#include
+#define MIR_LOG_COMPONENT "animator"
+#include
+
+using namespace miracle;
+using namespace std::chrono_literals;
+
+Animation::Animation(
+ miral::Window const& window,
+ miracle::AnimationType animation_type,
+ std::function 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 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 Animation::get_surface() const
+{
+ return window.operator std::weak_ptr();
+}
+
+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 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 const& callback)
+{
+ std::lock_guard 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 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(delta_time);
+
+ while(lag >= timestep) {
+ lag -= timestep;
+
+ std::vector update_data;
+ {
+ std::lock_guard 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);
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/animator.h b/src/animator.h
new file mode 100644
index 0000000..27e73b0
--- /dev/null
+++ b/src/animator.h
@@ -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 .
+**/
+
+#ifndef MIRACLEWM_ANIMATOR_H
+#define MIRACLEWM_ANIMATOR_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+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 const& callback);
+ Animation& operator=(Animation const& other);
+
+ AnimationStepResult step();
+ miral::Window const& get_window() const { return window; }
+ std::weak_ptr get_surface() const;
+ std::function get_callback() const { return callback; }
+
+private:
+ Animation(
+ miral::Window const& window,
+ AnimationType animation_type,
+ std::function 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 callback;
+};
+
+class Animator
+{
+public:
+ /// This will take the MainLoop to schedule animation.
+ explicit Animator(std::shared_ptr 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 const& callback);
+
+private:
+ void run();
+ std::shared_ptr server_action_queue;
+ std::vector queued_animations;
+ std::thread run_thread;
+ std::mutex processing_lock;
+ std::condition_variable cv;
+};
+
+} // miracle
+
+#endif // MIRACLEWM_ANIMATOR_H
diff --git a/src/main.cpp b/src/main.cpp
index f60593e..369ca41 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -37,7 +37,6 @@ along with this program. If not, see .
#include
#include
#include
-#include
using namespace miral;
diff --git a/src/policy.cpp b/src/policy.cpp
index 4f529c6..7ac4456 100644
--- a/src/policy.cpp
+++ b/src/policy.cpp
@@ -63,7 +63,8 @@ Policy::Policy(
i3_command_executor(*this, workspace_manager, tools),
surface_tracker{surface_tracker},
ipc { std::make_shared(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);
diff --git a/src/policy.h b/src/policy.h
index 1efcff9..25d84bc 100644
--- a/src/policy.h
+++ b/src/policy.h
@@ -26,6 +26,7 @@ along with this program. If not, see .
#include "window_metadata.h"
#include "workspace_manager.h"
#include "surface_tracker.h"
+#include "animator.h"
#include
#include
@@ -113,6 +114,7 @@ private:
WorkspaceObserverRegistrar workspace_observer_registrar;
WorkspaceManager workspace_manager;
std::shared_ptr ipc;
+ Animator animator;
WindowManagerToolsTilingInterface node_interface;
I3CommandExecutor i3_command_executor;
SurfaceTracker& surface_tracker;
diff --git a/src/renderer.cpp b/src/renderer.cpp
index e25e436..8e486f4 100644
--- a/src/renderer.cpp
+++ b/src/renderer.cpp
@@ -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(
diff --git a/src/tiling_window_tree.cpp b/src/tiling_window_tree.cpp
index a0d37ec..fa6f249 100644
--- a/src/tiling_window_tree.cpp
+++ b/src/tiling_window_tree.cpp
@@ -664,7 +664,7 @@ std::tuple, std::shared_ptr> 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);
diff --git a/src/window_manager_tools_tiling_interface.cpp b/src/window_manager_tools_tiling_interface.cpp
index 0f1cd87..aaa4514 100644
--- a/src/window_manager_tools_tiling_interface.cpp
+++ b/src/window_manager_tools_tiling_interface.cpp
@@ -19,12 +19,16 @@ along with this program. If not, see .
#include "leaf_node.h"
#include "window_helpers.h"
#include "window_metadata.h"
+#include "animator.h"
+#include
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())
+ {
+ 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)
diff --git a/src/window_manager_tools_tiling_interface.h b/src/window_manager_tools_tiling_interface.h
index 2a74c8f..9c022e0 100644
--- a/src/window_manager_tools_tiling_interface.h
+++ b/src/window_manager_tools_tiling_interface.h
@@ -23,10 +23,12 @@ along with this program. If not, see .
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;
};
}