#pragma once #include "Text.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); 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; } const std::string& get_title() const { return title; } const std::string& get_description() const { return description; } const std::string& get_author() const { return author; } // 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 thumbnail_is_local; std::unique_ptr title_text; std::unique_ptr description_text; std::unique_ptr author_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; private: std::string title; std::string description; std::string author; }; 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); 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 clear_thumbnails(); BodyItem* get_selected() const; 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); 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); float get_item_height(BodyItem *item); private: enum class LoadingState { NOT_LOADED, LOADING, FINISHED_LOADING, APPLIED_TO_TEXTURE }; struct ThumbnailData { bool referenced = false; LoadingState loading_state = LoadingState::NOT_LOADED; sf::Texture texture; std::unique_ptr image; // Set in another thread, and then reset after loading it into |texture| }; Program *program; void load_thumbnail_from_url(const std::string &url, bool local, sf::Vector2i thumbnail_resize_target_size, std::shared_ptr thumbnail_data); std::unordered_map> item_thumbnail_textures; bool loading_thumbnail; 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; }; }