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 0000000000..00e29b9959 Binary files /dev/null and b/Base/res/cursors/wait.f14t100.png differ diff --git a/Base/res/cursors/wait.png b/Base/res/cursors/wait.png deleted file mode 100644 index ac9a162270..0000000000 Binary files a/Base/res/cursors/wait.png and /dev/null differ 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);