mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 22:17:43 +00:00
WindowServer: Add a more generic mechanism for animations
This patch adds the WindowServer::Animation class, which represents a simple animation driven by the compositor. An animation has a length (in milliseconds) and two hooks: - on_update: called whenever the animation should render something. - on_stop: called when the animation is finished and/or stopped. This patch also ports the window minimization animation to this new mechanism. :^)
This commit is contained in:
parent
1f33c517df
commit
75f870a93f
7 changed files with 171 additions and 70 deletions
53
Userland/Services/WindowServer/Animation.cpp
Normal file
53
Userland/Services/WindowServer/Animation.cpp
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Animation.h"
|
||||||
|
#include "Compositor.h"
|
||||||
|
#include <AK/Badge.h>
|
||||||
|
|
||||||
|
namespace WindowServer {
|
||||||
|
|
||||||
|
Animation::Animation()
|
||||||
|
{
|
||||||
|
Compositor::the().register_animation({}, *this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Animation::~Animation()
|
||||||
|
{
|
||||||
|
Compositor::the().unregister_animation({}, *this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animation::set_length(int length_in_ms)
|
||||||
|
{
|
||||||
|
m_length = length_in_ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animation::start()
|
||||||
|
{
|
||||||
|
m_running = true;
|
||||||
|
m_timer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animation::stop()
|
||||||
|
{
|
||||||
|
m_running = false;
|
||||||
|
if (on_stop)
|
||||||
|
on_stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animation::update(Badge<Compositor>, Gfx::Painter& painter, Screen& screen, Gfx::DisjointRectSet& flush_rects)
|
||||||
|
{
|
||||||
|
int elapsed_ms = m_timer.elapsed();
|
||||||
|
float progress = min((float)elapsed_ms / (float)m_length, 1.0f);
|
||||||
|
|
||||||
|
if (on_update)
|
||||||
|
on_update(progress, painter, screen, flush_rects);
|
||||||
|
|
||||||
|
if (progress >= 1.0f)
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
48
Userland/Services/WindowServer/Animation.h
Normal file
48
Userland/Services/WindowServer/Animation.h
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/Forward.h>
|
||||||
|
#include <AK/Function.h>
|
||||||
|
#include <AK/NonnullRefPtr.h>
|
||||||
|
#include <AK/RefCounted.h>
|
||||||
|
#include <LibCore/ElapsedTimer.h>
|
||||||
|
#include <LibGfx/Forward.h>
|
||||||
|
|
||||||
|
namespace WindowServer {
|
||||||
|
|
||||||
|
class Compositor;
|
||||||
|
class Screen;
|
||||||
|
|
||||||
|
class Animation : public RefCounted<Animation> {
|
||||||
|
public:
|
||||||
|
static NonnullRefPtr<Animation> create() { return adopt_ref(*new Animation); }
|
||||||
|
|
||||||
|
~Animation();
|
||||||
|
|
||||||
|
bool is_running() const { return m_running; }
|
||||||
|
|
||||||
|
void start();
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
void set_length(int length_in_ms);
|
||||||
|
int length() const { return m_length; }
|
||||||
|
|
||||||
|
void update(Badge<Compositor>, Gfx::Painter&, Screen&, Gfx::DisjointRectSet& flush_rects);
|
||||||
|
|
||||||
|
Function<void(float progress, Gfx::Painter&, Screen&, Gfx::DisjointRectSet& flush_rects)> on_update;
|
||||||
|
Function<void()> on_stop;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Animation();
|
||||||
|
|
||||||
|
Core::ElapsedTimer m_timer;
|
||||||
|
int m_length { 0 };
|
||||||
|
bool m_running { false };
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ compile_ipc(WindowManagerServer.ipc WindowManagerServerEndpoint.h)
|
||||||
compile_ipc(WindowManagerClient.ipc WindowManagerClientEndpoint.h)
|
compile_ipc(WindowManagerClient.ipc WindowManagerClientEndpoint.h)
|
||||||
|
|
||||||
set(SOURCES
|
set(SOURCES
|
||||||
|
Animation.cpp
|
||||||
AppletManager.cpp
|
AppletManager.cpp
|
||||||
Button.cpp
|
Button.cpp
|
||||||
ClientConnection.cpp
|
ClientConnection.cpp
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "Compositor.h"
|
#include "Compositor.h"
|
||||||
|
#include "Animation.h"
|
||||||
#include "ClientConnection.h"
|
#include "ClientConnection.h"
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
#include "EventLoop.h"
|
#include "EventLoop.h"
|
||||||
|
@ -527,10 +528,9 @@ void Compositor::compose()
|
||||||
m_invalidated_window = false;
|
m_invalidated_window = false;
|
||||||
m_invalidated_cursor = false;
|
m_invalidated_cursor = false;
|
||||||
|
|
||||||
bool did_render_animation = false;
|
|
||||||
Screen::for_each([&](auto& screen) {
|
Screen::for_each([&](auto& screen) {
|
||||||
auto& screen_data = m_screen_data[screen.index()];
|
auto& screen_data = m_screen_data[screen.index()];
|
||||||
did_render_animation |= render_animation_frame(screen, screen_data.m_flush_special_rects);
|
update_animations(screen, screen_data.m_flush_special_rects);
|
||||||
return IterationDecision::Continue;
|
return IterationDecision::Continue;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -546,9 +546,6 @@ void Compositor::compose()
|
||||||
flush(screen);
|
flush(screen);
|
||||||
return IterationDecision::Continue;
|
return IterationDecision::Continue;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (did_render_animation)
|
|
||||||
step_animations();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Compositor::flush(Screen& screen)
|
void Compositor::flush(Screen& screen)
|
||||||
|
@ -709,60 +706,6 @@ void Compositor::ScreenData::flip_buffers(Screen& screen)
|
||||||
m_buffers_are_flipped = !m_buffers_are_flipped;
|
m_buffers_are_flipped = !m_buffers_are_flipped;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const int minimize_animation_steps = 10;
|
|
||||||
|
|
||||||
bool Compositor::render_animation_frame(Screen& screen, Gfx::DisjointRectSet& flush_rects)
|
|
||||||
{
|
|
||||||
bool did_render_any = false;
|
|
||||||
auto& painter = *m_screen_data[screen.index()].m_back_painter;
|
|
||||||
Gfx::PainterStateSaver saver(painter);
|
|
||||||
painter.set_draw_op(Gfx::Painter::DrawOp::Invert);
|
|
||||||
|
|
||||||
WindowManager::the().window_stack().for_each_window([&](Window& window) {
|
|
||||||
if (window.in_minimize_animation()) {
|
|
||||||
int animation_index = window.minimize_animation_index();
|
|
||||||
|
|
||||||
auto from_rect = window.is_minimized() ? window.frame().rect() : window.taskbar_rect();
|
|
||||||
auto to_rect = window.is_minimized() ? window.taskbar_rect() : window.frame().rect();
|
|
||||||
|
|
||||||
float x_delta_per_step = (float)(from_rect.x() - to_rect.x()) / minimize_animation_steps;
|
|
||||||
float y_delta_per_step = (float)(from_rect.y() - to_rect.y()) / minimize_animation_steps;
|
|
||||||
float width_delta_per_step = (float)(from_rect.width() - to_rect.width()) / minimize_animation_steps;
|
|
||||||
float height_delta_per_step = (float)(from_rect.height() - to_rect.height()) / minimize_animation_steps;
|
|
||||||
|
|
||||||
Gfx::IntRect rect {
|
|
||||||
from_rect.x() - (int)(x_delta_per_step * animation_index),
|
|
||||||
from_rect.y() - (int)(y_delta_per_step * animation_index),
|
|
||||||
from_rect.width() - (int)(width_delta_per_step * animation_index),
|
|
||||||
from_rect.height() - (int)(height_delta_per_step * animation_index)
|
|
||||||
};
|
|
||||||
|
|
||||||
dbgln_if(MINIMIZE_ANIMATION_DEBUG, "Minimize animation from {} to {} frame# {} {} on screen #{}", from_rect, to_rect, animation_index, rect, screen.index());
|
|
||||||
|
|
||||||
painter.draw_rect(rect, Color::Transparent); // Color doesn't matter, we draw inverted
|
|
||||||
flush_rects.add(rect.intersected(screen.rect()));
|
|
||||||
invalidate_screen(rect);
|
|
||||||
|
|
||||||
did_render_any = true;
|
|
||||||
}
|
|
||||||
return IterationDecision::Continue;
|
|
||||||
});
|
|
||||||
|
|
||||||
return did_render_any;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Compositor::step_animations()
|
|
||||||
{
|
|
||||||
WindowManager::the().window_stack().for_each_window([&](Window& window) {
|
|
||||||
if (window.in_minimize_animation()) {
|
|
||||||
window.step_minimize_animation();
|
|
||||||
if (window.minimize_animation_index() >= minimize_animation_steps)
|
|
||||||
window.end_minimize_animation();
|
|
||||||
}
|
|
||||||
return IterationDecision::Continue;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Compositor::screen_resolution_changed()
|
void Compositor::screen_resolution_changed()
|
||||||
{
|
{
|
||||||
// Screens may be gone now, invalidate any references to them
|
// Screens may be gone now, invalidate any references to them
|
||||||
|
@ -1260,4 +1203,24 @@ void Compositor::recompute_occlusions()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Compositor::register_animation(Badge<Animation>, Animation& animation)
|
||||||
|
{
|
||||||
|
auto result = m_animations.set(&animation);
|
||||||
|
VERIFY(result == AK::HashSetResult::InsertedNewEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compositor::unregister_animation(Badge<Animation>, Animation& animation)
|
||||||
|
{
|
||||||
|
bool was_removed = m_animations.remove(&animation);
|
||||||
|
VERIFY(was_removed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compositor::update_animations(Screen& screen, Gfx::DisjointRectSet& flush_rects)
|
||||||
|
{
|
||||||
|
auto& painter = *m_screen_data[screen.index()].m_back_painter;
|
||||||
|
for (RefPtr<Animation> animation : m_animations) {
|
||||||
|
animation->update({}, painter, screen, flush_rects);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
namespace WindowServer {
|
namespace WindowServer {
|
||||||
|
|
||||||
|
class Animation;
|
||||||
class ClientConnection;
|
class ClientConnection;
|
||||||
class Cursor;
|
class Cursor;
|
||||||
class MultiScaleBitmaps;
|
class MultiScaleBitmaps;
|
||||||
|
@ -95,11 +96,12 @@ public:
|
||||||
const Gfx::Bitmap* cursor_bitmap_for_screenshot(Badge<ClientConnection>, Screen&) const;
|
const Gfx::Bitmap* cursor_bitmap_for_screenshot(Badge<ClientConnection>, Screen&) const;
|
||||||
const Gfx::Bitmap& front_bitmap_for_screenshot(Badge<ClientConnection>, Screen&) const;
|
const Gfx::Bitmap& front_bitmap_for_screenshot(Badge<ClientConnection>, Screen&) const;
|
||||||
|
|
||||||
|
void register_animation(Badge<Animation>, Animation&);
|
||||||
|
void unregister_animation(Badge<Animation>, Animation&);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Compositor();
|
Compositor();
|
||||||
void init_bitmaps();
|
void init_bitmaps();
|
||||||
bool render_animation_frame(Screen&, Gfx::DisjointRectSet&);
|
|
||||||
void step_animations();
|
|
||||||
void invalidate_current_screen_number_rects();
|
void invalidate_current_screen_number_rects();
|
||||||
void overlays_theme_changed();
|
void overlays_theme_changed();
|
||||||
|
|
||||||
|
@ -114,6 +116,7 @@ private:
|
||||||
bool any_opaque_window_above_this_one_contains_rect(const Window&, const Gfx::IntRect&);
|
bool any_opaque_window_above_this_one_contains_rect(const Window&, const Gfx::IntRect&);
|
||||||
void change_cursor(const Cursor*);
|
void change_cursor(const Cursor*);
|
||||||
void flush(Screen&);
|
void flush(Screen&);
|
||||||
|
void update_animations(Screen&, Gfx::DisjointRectSet& flush_rects);
|
||||||
|
|
||||||
RefPtr<Core::Timer> m_compose_timer;
|
RefPtr<Core::Timer> m_compose_timer;
|
||||||
RefPtr<Core::Timer> m_immediate_compose_timer;
|
RefPtr<Core::Timer> m_immediate_compose_timer;
|
||||||
|
@ -195,6 +198,8 @@ private:
|
||||||
|
|
||||||
size_t m_show_screen_number_count { 0 };
|
size_t m_show_screen_number_count { 0 };
|
||||||
Optional<Gfx::Color> m_custom_background_color;
|
Optional<Gfx::Color> m_custom_background_color;
|
||||||
|
|
||||||
|
HashTable<Animation*> m_animations;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "Window.h"
|
#include "Window.h"
|
||||||
|
#include "Animation.h"
|
||||||
#include "AppletManager.h"
|
#include "AppletManager.h"
|
||||||
#include "ClientConnection.h"
|
#include "ClientConnection.h"
|
||||||
#include "Compositor.h"
|
#include "Compositor.h"
|
||||||
|
@ -306,6 +307,21 @@ void Window::set_taskbar_rect(const Gfx::IntRect& rect)
|
||||||
m_have_taskbar_rect = !m_taskbar_rect.is_empty();
|
m_have_taskbar_rect = !m_taskbar_rect.is_empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Gfx::IntRect interpolate_rect(Gfx::IntRect const& from_rect, Gfx::IntRect const& to_rect, float progress)
|
||||||
|
{
|
||||||
|
auto dx = to_rect.x() - from_rect.x();
|
||||||
|
auto dy = to_rect.y() - from_rect.y();
|
||||||
|
auto dw = to_rect.width() - from_rect.width();
|
||||||
|
auto dh = to_rect.height() - from_rect.height();
|
||||||
|
|
||||||
|
return Gfx::IntRect {
|
||||||
|
from_rect.x() + ((float)dx * progress),
|
||||||
|
from_rect.y() + ((float)dy * progress),
|
||||||
|
from_rect.width() + ((float)dw * progress),
|
||||||
|
from_rect.height() + ((float)dh * progress),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
void Window::start_minimize_animation()
|
void Window::start_minimize_animation()
|
||||||
{
|
{
|
||||||
if (!m_have_taskbar_rect) {
|
if (!m_have_taskbar_rect) {
|
||||||
|
@ -327,7 +343,26 @@ void Window::start_minimize_animation()
|
||||||
return IterationDecision::Continue;
|
return IterationDecision::Continue;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
m_minimize_animation_step = 0;
|
|
||||||
|
m_animation = Animation::create();
|
||||||
|
m_animation->set_length(150);
|
||||||
|
m_animation->on_update = [this](float progress, Gfx::Painter& painter, Screen& screen, Gfx::DisjointRectSet& flush_rects) {
|
||||||
|
Gfx::PainterStateSaver saver(painter);
|
||||||
|
painter.set_draw_op(Gfx::Painter::DrawOp::Invert);
|
||||||
|
|
||||||
|
auto from_rect = is_minimized() ? frame().rect() : taskbar_rect();
|
||||||
|
auto to_rect = is_minimized() ? taskbar_rect() : frame().rect();
|
||||||
|
|
||||||
|
auto rect = interpolate_rect(from_rect, to_rect, progress);
|
||||||
|
|
||||||
|
painter.draw_rect(rect, Color::Transparent); // Color doesn't matter, we draw inverted
|
||||||
|
flush_rects.add(rect.intersected(screen.rect()));
|
||||||
|
Compositor::the().invalidate_screen(rect);
|
||||||
|
};
|
||||||
|
m_animation->on_stop = [this] {
|
||||||
|
m_animation = nullptr;
|
||||||
|
};
|
||||||
|
m_animation->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Window::set_opacity(float opacity)
|
void Window::set_opacity(float opacity)
|
||||||
|
@ -1071,5 +1106,4 @@ String Window::computed_title() const
|
||||||
return String::formatted("{} (Not responding)", title);
|
return String::formatted("{} (Not responding)", title);
|
||||||
return title;
|
return title;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
|
|
||||||
namespace WindowServer {
|
namespace WindowServer {
|
||||||
|
|
||||||
|
class Animation;
|
||||||
class ClientConnection;
|
class ClientConnection;
|
||||||
class Cursor;
|
class Cursor;
|
||||||
class KeyEvent;
|
class KeyEvent;
|
||||||
|
@ -256,11 +257,7 @@ public:
|
||||||
Gfx::DisjointRectSet take_pending_paint_rects() { return move(m_pending_paint_rects); }
|
Gfx::DisjointRectSet take_pending_paint_rects() { return move(m_pending_paint_rects); }
|
||||||
|
|
||||||
bool has_taskbar_rect() const { return m_have_taskbar_rect; };
|
bool has_taskbar_rect() const { return m_have_taskbar_rect; };
|
||||||
bool in_minimize_animation() const { return m_minimize_animation_step != -1; }
|
|
||||||
int minimize_animation_index() const { return m_minimize_animation_step; }
|
|
||||||
void step_minimize_animation() { m_minimize_animation_step += 1; }
|
|
||||||
void start_minimize_animation();
|
void start_minimize_animation();
|
||||||
void end_minimize_animation() { m_minimize_animation_step = -1; }
|
|
||||||
|
|
||||||
Gfx::IntRect tiled_rect(Screen*, WindowTileType) const;
|
Gfx::IntRect tiled_rect(Screen*, WindowTileType) const;
|
||||||
void recalculate_rect();
|
void recalculate_rect();
|
||||||
|
@ -408,10 +405,10 @@ private:
|
||||||
MenuItem* m_window_menu_move_item { nullptr };
|
MenuItem* m_window_menu_move_item { nullptr };
|
||||||
MenuItem* m_window_menu_close_item { nullptr };
|
MenuItem* m_window_menu_close_item { nullptr };
|
||||||
MenuItem* m_window_menu_menubar_visibility_item { nullptr };
|
MenuItem* m_window_menu_menubar_visibility_item { nullptr };
|
||||||
int m_minimize_animation_step { -1 };
|
|
||||||
Optional<int> m_progress;
|
Optional<int> m_progress;
|
||||||
bool m_should_show_menubar { true };
|
bool m_should_show_menubar { true };
|
||||||
WindowStack* m_outer_stack { nullptr };
|
WindowStack* m_outer_stack { nullptr };
|
||||||
|
RefPtr<Animation> m_animation;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using List = IntrusiveList<Window, RawPtr<Window>, &Window::m_list_node>;
|
using List = IntrusiveList<Window, RawPtr<Window>, &Window::m_list_node>;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue