From 07badd953037b49ac754a644c59ae9531754fce1 Mon Sep 17 00:00:00 2001 From: Tom Date: Wed, 16 Dec 2020 22:15:14 -0700 Subject: [PATCH] WindowServer: Add the ability to animate cursors This adds the ability to specify cursor attributes as part of their file names, which allows us to remove hard coded values like the hot spot from the code. The attributes can be specified between the last two dots of the file name. Each attribute begins with a character, followed by one or more digits that specify a uint value. Supported attributes: x: The x-coordinate of the cursor hotspot y: The y-coordinate of the cursor hotspot f: The number of animated frames horizontally in the image t: The number of milliseconds per frame For example, the filename wait.f14t100.png specifies that the image contains 14 frames that should be cycled through at a rate of 100ms. The hotspot is not specified, so it defaults to the center. --- Base/etc/WindowServer/WindowServer.ini | 8 +- .../res/cursors/{arrow.png => arrow.x2y2.png} | Bin Base/res/cursors/{hand.png => hand.x8y4.png} | Bin Base/res/cursors/{help.png => help.x1y1.png} | Bin Base/res/cursors/wait.f14t100.png | Bin 0 -> 2780 bytes Base/res/cursors/wait.png | Bin 4236 -> 0 bytes Services/WindowServer/Compositor.cpp | 44 +++++++- Services/WindowServer/Compositor.h | 7 +- Services/WindowServer/Cursor.cpp | 105 +++++++++++++++++- Services/WindowServer/Cursor.h | 39 ++++++- Services/WindowServer/WindowManager.cpp | 23 ++-- Services/WindowServer/WindowManager.h | 1 - 12 files changed, 188 insertions(+), 39 deletions(-) rename Base/res/cursors/{arrow.png => arrow.x2y2.png} (100%) rename Base/res/cursors/{hand.png => hand.x8y4.png} (100%) rename Base/res/cursors/{help.png => help.x1y1.png} (100%) create mode 100644 Base/res/cursors/wait.f14t100.png delete mode 100644 Base/res/cursors/wait.png diff --git a/Base/etc/WindowServer/WindowServer.ini b/Base/etc/WindowServer/WindowServer.ini index 9120a3dbd0..c091fefeff 100644 --- a/Base/etc/WindowServer/WindowServer.ini +++ b/Base/etc/WindowServer/WindowServer.ini @@ -7,7 +7,7 @@ Name=Default [Cursor] Hidden=/res/cursors/hidden.png -Arrow=/res/cursors/arrow.png +Arrow=/res/cursors/arrow.x2y2.png ResizeH=/res/cursors/resize-horizontal.png ResizeV=/res/cursors/resize-vertical.png ResizeDTLBR=/res/cursors/resize-diagonal-tlbr.png @@ -17,10 +17,10 @@ ResizeRow=/res/cursors/resize-row.png IBeam=/res/cursors/i-beam.png Disallowed=/res/cursors/disallowed.png Move=/res/cursors/move.png -Hand=/res/cursors/hand.png -Help=/res/cursors/help.png +Hand=/res/cursors/hand.x8y4.png +Help=/res/cursors/help.x1y1.png Drag=/res/cursors/drag.png -Wait=/res/cursors/wait.png +Wait=/res/cursors/wait.f14t100.png Crosshair=/res/cursors/crosshair.png [Input] diff --git a/Base/res/cursors/arrow.png b/Base/res/cursors/arrow.x2y2.png similarity index 100% rename from Base/res/cursors/arrow.png rename to Base/res/cursors/arrow.x2y2.png diff --git a/Base/res/cursors/hand.png b/Base/res/cursors/hand.x8y4.png similarity index 100% rename from Base/res/cursors/hand.png rename to Base/res/cursors/hand.x8y4.png diff --git a/Base/res/cursors/help.png b/Base/res/cursors/help.x1y1.png similarity index 100% rename from Base/res/cursors/help.png rename to Base/res/cursors/help.x1y1.png diff --git a/Base/res/cursors/wait.f14t100.png b/Base/res/cursors/wait.f14t100.png new file mode 100644 index 0000000000000000000000000000000000000000..00e29b9959dd175d12303eceb771eb8129730035 GIT binary patch literal 2780 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 DW(GD~ literal 0 HcmV?d00001 diff --git a/Base/res/cursors/wait.png b/Base/res/cursors/wait.png deleted file mode 100644 index ac9a162270d0880a7f438c67cd90d0f9b631e35f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4236 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 diff --git a/Services/WindowServer/Compositor.cpp b/Services/WindowServer/Compositor.cpp index 7c023409da..b4450f1f48 100644 --- a/Services/WindowServer/Compositor.cpp +++ b/Services/WindowServer/Compositor.cpp @@ -118,6 +118,12 @@ void Compositor::compose() m_wallpaper_mode = mode_to_enum(wm.config()->read_entry("Background", "Mode", "simple")); auto& ws = Screen::the(); + { + auto& current_cursor = wm.active_cursor(); + if (m_current_cursor != ¤t_cursor) + change_cursor(¤t_cursor); + } + if (!m_invalidated_any) { // nothing dirtied since the last compose pass. return; @@ -705,17 +711,21 @@ bool Compositor::set_resolution(int desired_width, int desired_height) Gfx::IntRect Compositor::current_cursor_rect() const { auto& wm = WindowManager::the(); - return { Screen::the().cursor_location().translated(-wm.active_cursor().hotspot()), wm.active_cursor().size() }; + auto& current_cursor = m_current_cursor ? *m_current_cursor : wm.active_cursor(); + return { Screen::the().cursor_location().translated(-current_cursor.params().hotspot()), current_cursor.size() }; } -void Compositor::invalidate_cursor() +void Compositor::invalidate_cursor(bool compose_immediately) { if (m_invalidated_cursor) return; m_invalidated_cursor = true; m_invalidated_any = true; - start_compose_async_timer(); + if (compose_immediately) + compose(); + else + start_compose_async_timer(); } bool Compositor::draw_geometry_label(Gfx::IntRect& geometry_label_rect) @@ -742,6 +752,29 @@ bool Compositor::draw_geometry_label(Gfx::IntRect& geometry_label_rect) return true; } +void Compositor::change_cursor(const Cursor* cursor) +{ + if (m_current_cursor == cursor) + return; + m_current_cursor = cursor; + m_current_cursor_frame = 0; + if (m_cursor_timer) { + m_cursor_timer->stop(); + m_cursor_timer = nullptr; + } + if (cursor && cursor->params().frames() > 1 && cursor->params().frame_ms() != 0) { + m_cursor_timer = add( + cursor->params().frame_ms(), [this, cursor] { + if (m_current_cursor != cursor) + return; + auto frames = cursor->params().frames(); + if (++m_current_cursor_frame >= frames) + m_current_cursor_frame = 0; + invalidate_cursor(true); + }); + } +} + void Compositor::draw_cursor(const Gfx::IntRect& cursor_rect) { auto& wm = WindowManager::the(); @@ -751,9 +784,10 @@ void Compositor::draw_cursor(const Gfx::IntRect& cursor_rect) m_cursor_back_painter = make(*m_cursor_back_bitmap); } - m_cursor_back_painter->blit({ 0, 0 }, *m_back_bitmap, wm.active_cursor().rect().translated(cursor_rect.location()).intersected(Screen::the().rect())); + auto& current_cursor = m_current_cursor ? *m_current_cursor : wm.active_cursor(); + m_cursor_back_painter->blit({ 0, 0 }, *m_back_bitmap, current_cursor.rect().translated(cursor_rect.location()).intersected(Screen::the().rect())); auto& back_painter = *m_back_painter; - back_painter.blit(cursor_rect.location(), wm.active_cursor().bitmap(), wm.active_cursor().rect()); + back_painter.blit(cursor_rect.location(), current_cursor.bitmap(), current_cursor.source_rect(m_current_cursor_frame)); m_last_cursor_rect = cursor_rect; } diff --git a/Services/WindowServer/Compositor.h b/Services/WindowServer/Compositor.h index d0e85c0bfb..18bbe8fe0d 100644 --- a/Services/WindowServer/Compositor.h +++ b/Services/WindowServer/Compositor.h @@ -65,7 +65,7 @@ public: bool set_wallpaper(const String& path, Function&& callback); String wallpaper_path() const { return m_wallpaper_path; } - void invalidate_cursor(); + void invalidate_cursor(bool = false); Gfx::IntRect current_cursor_rect() const; void increment_display_link_count(Badge); @@ -84,6 +84,7 @@ private: void start_compose_async_timer(); void recompute_occlusions(); bool any_opaque_window_above_this_one_contains_rect(const Window&, const Gfx::IntRect&); + void change_cursor(const Cursor*); void draw_cursor(const Gfx::IntRect&); void restore_cursor_back(); bool draw_geometry_label(Gfx::IntRect&); @@ -118,6 +119,10 @@ private: WallpaperMode m_wallpaper_mode { WallpaperMode::Unchecked }; RefPtr m_wallpaper; + const Cursor* m_current_cursor { nullptr }; + unsigned m_current_cursor_frame { 0 }; + RefPtr m_cursor_timer; + RefPtr m_display_link_notify_timer; size_t m_display_link_count { 0 }; }; diff --git a/Services/WindowServer/Cursor.cpp b/Services/WindowServer/Cursor.cpp index 78d5bff74e..236c27a50d 100644 --- a/Services/WindowServer/Cursor.cpp +++ b/Services/WindowServer/Cursor.cpp @@ -24,15 +24,106 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include #include #include namespace WindowServer { -Cursor::Cursor(NonnullRefPtr&& bitmap, const Gfx::IntPoint& hotspot) - : m_bitmap(move(bitmap)) - , m_hotspot(hotspot) +CursorParams CursorParams::parse_from_file_name(const StringView& cursor_path, const Gfx::IntPoint& default_hotspot) { + LexicalPath path(cursor_path); + if (!path.is_valid()) { + dbg() << "Cannot parse invalid cursor path, use default cursor params"; + return { default_hotspot }; + } + auto file_title = path.title(); + auto last_dot_in_title = StringView(file_title).find_last_of('.'); + if (!last_dot_in_title.has_value() || last_dot_in_title.value() == 0) { + // No encoded params in filename. Not an error, we'll just use defaults + return { default_hotspot }; + } + auto params_str = file_title.substring_view(last_dot_in_title.value() + 1); + + CursorParams params(default_hotspot); + for (size_t i = 0; i + 1 < params_str.length();) { + auto property = params_str[i++]; + + auto value = [&]() -> Optional { + size_t k = i; + while (k < params_str.length()) { + auto ch = params_str[k]; + if (ch < '0' || ch > '9') + break; + k++; + } + if (k == i) + return {}; + auto parsed_number = params_str.substring_view(i, k - i).to_uint(); + if (!parsed_number.has_value()) + return {}; + i = k; + return parsed_number.value(); + }(); + if (!value.has_value()) { + dbg() << "Failed to parse value for property '" << property << "' from parsed cursor path: " << cursor_path; + return { default_hotspot }; + } + switch (property) { + case 'x': + params.m_hotspot.set_x(value.value()); + params.m_have_hotspot = true; + break; + case 'y': + params.m_hotspot.set_y(value.value()); + params.m_have_hotspot = true; + break; + case 'f': + if (value.value() > 1) + params.m_frames = value.value(); + break; + case 't': + if (value.value() >= 100 && value.value() <= 1000) + params.m_frame_ms = value.value(); + else + dbg() << "Cursor frame rate outside of valid range (100-1000ms)"; + break; + default: + dbg() << "Ignore unknown property '" << property << "' with value " << value.value() << " parsed from cursor path: " << cursor_path; + return { default_hotspot }; + } + } + return params; +} + +CursorParams CursorParams::constrained(const Gfx::Bitmap& bitmap) const +{ + CursorParams params(*this); + auto rect = bitmap.rect(); + if (params.m_frames > 1) { + if (rect.width() % params.m_frames == 0) { + rect.set_width(rect.width() / (int)params.m_frames); + } else { + dbg() << "Cannot divide cursor dimensions " << rect << " into " << params.m_frames << " frames"; + params.m_frames = 1; + } + } + if (params.m_have_hotspot) + params.m_hotspot = params.m_hotspot.constrained(rect); + else + params.m_hotspot = rect.center(); + return params; +} + +Cursor::Cursor(NonnullRefPtr&& bitmap, const CursorParams& cursor_params) + : m_bitmap(move(bitmap)) + , m_params(cursor_params.constrained(*m_bitmap)) + , m_rect(m_bitmap->rect()) +{ + if (m_params.frames() > 1) { + ASSERT(m_rect.width() % m_params.frames() == 0); + m_rect.set_width(m_rect.width() / m_params.frames()); + } } Cursor::~Cursor() @@ -41,12 +132,14 @@ Cursor::~Cursor() NonnullRefPtr Cursor::create(NonnullRefPtr&& bitmap) { - return adopt(*new Cursor(move(bitmap), bitmap->rect().center())); + auto hotspot = bitmap->rect().center(); + return adopt(*new Cursor(move(bitmap), CursorParams(hotspot))); } -NonnullRefPtr Cursor::create(NonnullRefPtr&& bitmap, const Gfx::IntPoint& hotspot) +NonnullRefPtr Cursor::create(NonnullRefPtr&& bitmap, const StringView& filename) { - return adopt(*new Cursor(move(bitmap), hotspot)); + auto default_hotspot = bitmap->rect().center(); + return adopt(*new Cursor(move(bitmap), CursorParams::parse_from_file_name(filename, default_hotspot))); } RefPtr Cursor::create(Gfx::StandardCursor standard_cursor) diff --git a/Services/WindowServer/Cursor.h b/Services/WindowServer/Cursor.h index 384ba357b6..efe014a735 100644 --- a/Services/WindowServer/Cursor.h +++ b/Services/WindowServer/Cursor.h @@ -31,24 +31,51 @@ namespace WindowServer { +class CursorParams { +public: + static CursorParams parse_from_file_name(const StringView&, const Gfx::IntPoint&); + CursorParams(const Gfx::IntPoint& hotspot) + : m_hotspot(hotspot) + { + } + CursorParams constrained(const Gfx::Bitmap&) const; + + const Gfx::IntPoint& hotspot() const { return m_hotspot; } + unsigned frames() const { return m_frames; } + unsigned frame_ms() const { return m_frame_ms; } + +private: + CursorParams() = default; + Gfx::IntPoint m_hotspot; + unsigned m_frames { 1 }; + unsigned m_frame_ms { 0 }; + bool m_have_hotspot { false }; +}; + class Cursor : public RefCounted { public: - static NonnullRefPtr create(NonnullRefPtr&&, const Gfx::IntPoint& hotspot); + static NonnullRefPtr create(NonnullRefPtr&&, const StringView&); static NonnullRefPtr create(NonnullRefPtr&&); static RefPtr create(Gfx::StandardCursor); ~Cursor(); - Gfx::IntPoint hotspot() const { return m_hotspot; } + const CursorParams& params() const { return m_params; } const Gfx::Bitmap& bitmap() const { return *m_bitmap; } - Gfx::IntRect rect() const { return m_bitmap->rect(); } - Gfx::IntSize size() const { return m_bitmap->size(); } + Gfx::IntRect source_rect(unsigned frame) const + { + return m_rect.translated(frame * m_rect.width(), 0); + } + + Gfx::IntRect rect() const { return m_rect; } + Gfx::IntSize size() const { return m_rect.size(); } private: - Cursor(NonnullRefPtr&&, const Gfx::IntPoint&); + Cursor(NonnullRefPtr&&, const CursorParams&); RefPtr m_bitmap; - Gfx::IntPoint m_hotspot; + CursorParams m_params; + Gfx::IntRect m_rect; }; } diff --git a/Services/WindowServer/WindowManager.cpp b/Services/WindowServer/WindowManager.cpp index b25c72c82b..40dccb5b93 100644 --- a/Services/WindowServer/WindowManager.cpp +++ b/Services/WindowServer/WindowManager.cpp @@ -83,23 +83,14 @@ WindowManager::~WindowManager() { } -NonnullRefPtr WindowManager::get_cursor(const String& name, const Gfx::IntPoint& hotspot) -{ - auto path = m_config->read_entry("Cursor", name, "/res/cursors/arrow.png"); - auto gb = Gfx::Bitmap::load_from_file(path); - if (gb) - return Cursor::create(*gb, hotspot); - return Cursor::create(*Gfx::Bitmap::load_from_file("/res/cursors/arrow.png")); -} - NonnullRefPtr WindowManager::get_cursor(const String& name) { - auto path = m_config->read_entry("Cursor", name, "/res/cursors/arrow.png"); + static const auto s_default_cursor_path = "/res/cursors/arrow.x2y2.png"; + auto path = m_config->read_entry("Cursor", name, s_default_cursor_path); auto gb = Gfx::Bitmap::load_from_file(path); - if (gb) - return Cursor::create(*gb); - return Cursor::create(*Gfx::Bitmap::load_from_file("/res/cursors/arrow.png")); + return Cursor::create(*gb, path); + return Cursor::create(*Gfx::Bitmap::load_from_file(s_default_cursor_path), s_default_cursor_path); } void WindowManager::reload_config(bool set_screen) @@ -113,9 +104,9 @@ void WindowManager::reload_config(bool set_screen) } m_hidden_cursor = get_cursor("Hidden"); - m_arrow_cursor = get_cursor("Arrow", { 2, 2 }); - m_hand_cursor = get_cursor("Hand", { 8, 4 }); - m_help_cursor = get_cursor("Help", { 1, 1 }); + m_arrow_cursor = get_cursor("Arrow"); + m_hand_cursor = get_cursor("Hand"); + m_help_cursor = get_cursor("Help"); m_resize_horizontally_cursor = get_cursor("ResizeH"); m_resize_vertically_cursor = get_cursor("ResizeV"); m_resize_diagonally_tlbr_cursor = get_cursor("ResizeDTLBR"); diff --git a/Services/WindowServer/WindowManager.h b/Services/WindowServer/WindowManager.h index 2af28e5639..5bd3c7ee4a 100644 --- a/Services/WindowServer/WindowManager.h +++ b/Services/WindowServer/WindowManager.h @@ -215,7 +215,6 @@ public: private: NonnullRefPtr get_cursor(const String& name); - NonnullRefPtr get_cursor(const String& name, const Gfx::IntPoint& hotspot); void process_mouse_event(MouseEvent&, Window*& hovered_window); void process_event_for_doubleclick(Window& window, MouseEvent& event);