/* * Copyright (c) 2023, Bastiaan van der Plaat * Copyright (c) 2023, Jelle Raaijmakers * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include #include #include #include #include #include #include namespace Maps { class MapWidget : public GUI::Frame , public Config::Listener { C_OBJECT(MapWidget); public: struct LatLng { double latitude; double longitude; double distance_to(LatLng const& other) const; }; struct LatLngBounds { LatLng north_west; LatLng south_east; int get_zoom() const; }; struct Options { Optional tile_provider {}; LatLng center; int zoom; bool context_menu_enabled { true }; bool scale_enabled { true }; int scale_max_width { 100 }; bool attribution_enabled { true }; Optional attribution_text {}; Optional attribution_url {}; }; LatLng center() const { return m_center; } void set_center(LatLng const& center) { m_center = { min(max(center.latitude, -LATITUDE_MAX), LATITUDE_MAX), min(max(center.longitude, -180.0), 180.0) }; update(); } int zoom() const { return m_zoom; } void set_zoom(int zoom); struct Marker { LatLng latlng; Optional tooltip {}; RefPtr image { nullptr }; Optional name {}; }; void add_marker(Marker const& marker) { m_markers.append(marker); update(); } void remove_markers_with_name(StringView name) { m_markers.remove_all_matching([name](auto const& marker) { return marker.name == name; }); update(); } struct Panel { enum class Position { TopLeft, TopRight, BottomLeft, BottomRight, }; String text; Position position; Optional url {}; Optional name {}; Gfx::IntRect rect { 0, 0, 0, 0 }; }; void add_panel(Panel const& panel) { m_panels.append(panel); update(); } void remove_panels_with_name(StringView name) { m_panels.remove_all_matching([name](auto const& panel) { return panel.name == name; }); update(); } LatLng context_menu_latlng() const { return m_context_menu_latlng; } void add_context_menu_action(NonnullRefPtr const& action) { m_context_menu_actions.append(action); } struct TileKey { int x; int y; int zoom; unsigned hash() const { return pair_int_hash(x, pair_int_hash(y, zoom)); } bool operator==(TileKey const& other) const { return x == other.x && y == other.y && zoom == other.zoom; } }; enum class TileDownloadBehavior { DoNotDownload, Download, }; protected: MapWidget(Options const&); RefPtr request_client() const { return m_request_client; } private: virtual void config_string_did_change(StringView domain, StringView group, StringView key, StringView value) override; virtual void doubleclick_event(GUI::MouseEvent&) override; virtual void mousemove_event(GUI::MouseEvent&) override; virtual void mousedown_event(GUI::MouseEvent&) override; virtual void mouseup_event(GUI::MouseEvent&) override; virtual void mousewheel_event(GUI::MouseEvent&) override; virtual void context_menu_event(GUI::ContextMenuEvent& event) override; virtual void paint_event(GUI::PaintEvent&) override; void set_zoom_for_mouse_event(int zoom, GUI::MouseEvent&); Optional> get_tile_image(int x, int y, int zoom, TileDownloadBehavior); void process_tile_queue(); void clear_tile_queue(); void paint_map(GUI::Painter&); void paint_scale_line(GUI::Painter&, String label, Gfx::IntRect rect); void paint_scale(GUI::Painter&); void paint_panels(GUI::Painter&); static int constexpr TILE_SIZE = 256; static double constexpr LATITUDE_MAX = 85.0511287798066; static int constexpr EARTH_RADIUS = 6371000.0; static size_t constexpr TILES_CACHE_MAX = 256; static constexpr size_t TILES_DOWNLOAD_PARALLEL_MAX = 8; static int constexpr ZOOM_MIN = 2; static int constexpr ZOOM_MAX = 19; static int constexpr PANEL_PADDING_X = 6; static int constexpr PANEL_PADDING_Y = 4; // These colors match the default OpenStreetMap map tiles style, so they don't depend on any system theme colors static Gfx::Color constexpr map_background_color = { 200, 200, 200 }; static Gfx::Color constexpr panel_background_color = { 255, 255, 255, 204 }; static Gfx::Color constexpr panel_foreground_color = { 51, 51, 51 }; RefPtr m_request_client; Vector, TILES_DOWNLOAD_PARALLEL_MAX> m_active_requests; Queue m_tile_queue; RefPtr m_marker_image; Optional m_tile_provider; String m_default_tile_provider; LatLng m_center; int m_zoom {}; bool m_context_menu_enabled {}; RefPtr m_context_menu; LatLng m_context_menu_latlng; Vector> m_context_menu_actions; bool m_scale_enabled {}; int m_scale_max_width {}; bool m_attribution_enabled {}; URL m_attribution_url; bool m_dragging { false }; int m_last_mouse_x { 0 }; int m_last_mouse_y { 0 }; bool m_first_image_loaded { false }; bool m_connection_failed { false }; OrderedHashMap> m_tiles; Vector m_markers; Vector m_panels; }; } template<> struct AK::Traits : public DefaultTraits { static unsigned hash(Maps::MapWidget::TileKey const& t) { return t.hash(); } };