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; }; }