#pragma once #include "Text.hpp" #include "AsyncImageLoader.hpp" #include #include #include #include #include "../external/RoundedRectangleShape.hpp" #include #include #include namespace sf { class RenderWindow; } namespace QuickMedia { class Program; class BodyItem { public: BodyItem(std::string _title); BodyItem(const BodyItem &other); static std::shared_ptr create(std::string title) { return std::make_shared(std::move(title)); } void set_title(std::string new_title) { if(title.empty() && new_title.empty()) return; title = std::move(new_title); dirty = true; } void append_title(std::string str) { title += str; dirty = true; } void set_description(std::string new_description) { if(description.empty() && new_description.empty()) return; description = std::move(new_description); dirty_description = true; } void append_description(std::string str) { description += str; dirty_description = true; } void set_author(std::string new_author) { if(author.empty() && new_author.empty()) return; author = std::move(new_author); dirty_author = true; } void set_timestamp(time_t new_timestamp) { if(timestamp == 0 && new_timestamp == 0) return; timestamp = new_timestamp; dirty_timestamp = true; } const std::string& get_title() const { return title; } const std::string& get_description() const { return description; } const std::string& get_author() const { return author; } time_t get_timestamp() const { return timestamp; } // TODO: Use a list of strings instead, not all plugins need all of these fields std::string url; std::string thumbnail_url; std::string attached_content_url; // TODO: Remove and use |url| instead bool visible; bool dirty; bool dirty_description; bool dirty_author; bool dirty_timestamp; bool thumbnail_is_local; std::unique_ptr title_text; std::unique_ptr description_text; std::unique_ptr author_text; std::unique_ptr timestamp_text; // Used by image boards for example. The elements are indices to other body items std::vector replies; std::string post_number; sf::Color title_color; sf::Color author_color; void *userdata; // Not managed, should be deallocated by whoever sets this sf::Int32 last_drawn_time; private: std::string title; std::string description; std::string author; time_t timestamp; }; using BodyItems = std::vector>; class Body { public: Body(Program *program, sf::Font *font, sf::Font *bold_font, sf::Font *cjk_font); ~Body(); // Select previous page, ignoring invisible items. Returns true if the item was changed. This can be used to check if the top was hit when wrap_around is set to false bool select_previous_page(); // Select next page, ignoring invisible items. Returns true if the item was changed. This can be used to check if the bottom was hit when wrap_around is set to false bool select_next_page(); // Select previous item, ignoring invisible items. Returns true if the item was changed. This can be used to check if the top was hit when wrap_around is set to false bool select_previous_item(); // Select next item, ignoring invisible items. Returns true if the item was changed. This can be used to check if the bottom was hit when wrap_around is set to false bool select_next_item(); void set_selected_item(int item); // Returns -1 if item can't be found int get_index_by_body_item(BodyItem *body_item); void select_first_item(); void select_last_item(); void reset_selected(); void clear_items(); void prepend_items(BodyItems new_items); void append_items(BodyItems new_items); void insert_item_by_timestamp(std::shared_ptr body_item); void insert_items_by_timestamps(BodyItems new_items); void clear_thumbnails(); BodyItem* get_selected() const; std::shared_ptr get_selected_shared(); // Returns null if not visible item BodyItem* get_last_fully_visible_item(); void clamp_selection(); void draw(sf::RenderWindow &window, sf::Vector2f pos, sf::Vector2f size); void draw(sf::RenderWindow &window, sf::Vector2f pos, sf::Vector2f size, const Json::Value &content_progress); // |size| is the clip size, another outside this will be cut off. // Note: this should be called after |draw|, or thumbnails will be messed up. TODO: find a way to solve this issue in a clean way. // This happens because of |draw| sets thumbnails as unreferenced at the beginning and cleans them up at the end if they are not drawn in the same function call. void draw_item(sf::RenderWindow &window, BodyItem *item, sf::Vector2f pos, sf::Vector2f size); float get_item_height(BodyItem *item); float get_spacing_y() const; static bool string_find_case_insensitive(const std::string &str, const std::string &substr); // TODO: Make this actually fuzzy... Right now it's just a case insensitive string find. // This would require reordering the body. // TODO: Highlight the part of the text that matches the search. // TODO: Ignore dot, whitespace and special characters void filter_search_fuzzy(const std::string &text); bool no_items_visible() const; int get_selected_item() const { return selected_item; } void set_page_scroll(float scroll) { page_scroll = scroll; } float get_page_scroll() const { return page_scroll; } bool is_last_item_fully_visible() const { return last_item_fully_visible; } sf::Font *font; sf::Font *bold_font; sf::Font *cjk_font; sf::Text progress_text; sf::Text replies_text; BodyItems items; bool draw_thumbnails; bool wrap_around; // Set to {0, 0} to disable resizing sf::Vector2i thumbnail_resize_target_size; sf::Vector2f thumbnail_fallback_size; sf::Color line_seperator_color; private: void draw_item(sf::RenderWindow &window, BodyItem *item, const sf::Vector2f &pos, const sf::Vector2f &size, const float item_height, const int item_index, const Json::Value &content_progress); void update_dirty_state(BodyItem *body_item, sf::Vector2f size); void clear_body_item_cache(BodyItem *body_item); private: Program *program; std::unordered_map> item_thumbnail_textures; AsyncImageLoader async_image_loader; int selected_item; int prev_selected_item; float page_scroll; // TODO: Use a loading gif or something similar instead, to properly indicate the image is loading. Which could also have text that says "failed to load image" when image loading failed. sf::RectangleShape image_fallback; sf::RectangleShape item_background_shadow; sf::RoundedRectangleShape item_background; sf::Sprite image; std::future load_thumbnail_future; int num_visible_items; bool last_item_fully_visible; int last_fully_visible_item; sf::Clock draw_timer; }; }