From 5465c09cd108e37720dbad139de98bdcf5dfe8bf Mon Sep 17 00:00:00 2001 From: dec05eba Date: Mon, 19 Apr 2021 16:13:55 +0200 Subject: Move tab code to separate class, fix upload time missing for certain manga plugins, fix touch room click messed up --- src/Body.cpp | 16 ++- src/QuickMedia.cpp | 312 ++++++++++++++++++------------------------ src/SearchBar.cpp | 9 +- src/Tabs.cpp | 200 +++++++++++++++++++++++++++ src/plugins/MangaCombined.cpp | 4 +- src/plugins/MangaGeneric.cpp | 5 +- src/plugins/Manganelo.cpp | 2 +- 7 files changed, 357 insertions(+), 191 deletions(-) create mode 100644 src/Tabs.cpp (limited to 'src') diff --git a/src/Body.cpp b/src/Body.cpp index 0971e00..c008c40 100644 --- a/src/Body.cpp +++ b/src/Body.cpp @@ -258,9 +258,9 @@ namespace QuickMedia { if(item != selected_item) selected_scrolled = 0.0f; selected_item = item; + clamp_selection(); if(reset_prev_selected_item) prev_selected_item = selected_item; - clamp_selection(); clamp_selected_item_to_body_count = 1; //page_scroll = 0.0f; } @@ -759,8 +759,6 @@ namespace QuickMedia { if(!items_cut_off_set) items_cut_off = false; - mouse_left_clicked = false; - for(auto it = item_thumbnail_textures.begin(); it != item_thumbnail_textures.end();) { if(!it->second->referenced) { it = item_thumbnail_textures.erase(it); @@ -784,6 +782,14 @@ namespace QuickMedia { loaded_textures_changed = false; malloc_trim(0); } + + mouse_left_clicked = false; + if(clicked_body_item) { + auto clicked_body_item_tmp = clicked_body_item; // tmp because below call to body_item_select_callback may call this same draw function + clicked_body_item = nullptr; + if(body_item_select_callback) + body_item_select_callback(clicked_body_item_tmp.get()); + } } void Body::update_dirty_state(BodyItem *body_item, float width) { @@ -953,12 +959,12 @@ namespace QuickMedia { item_pos.x = std::floor(pos.x); item_pos.y = std::floor(pos.y); - if(body_item_select_callback && mouse_left_clicked) { + if(body_item_select_callback && mouse_left_clicked && !clicked_body_item) { sf::FloatRect item_box(pos, sf::Vector2f(size.x, item_height)); // TODO: Scale mouse_press_pixels_moved_abs with monitor PPI instead of using get_ui_scale() if(item_box.contains(mouse_click_pos) && item_box.contains(mouse_release_pos) && mouse_press_pixels_moved_abs <= 50.0 * get_ui_scale()) { + clicked_body_item = items[item_index]; set_selected_item(item_index, false); - body_item_select_callback(item); } } diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 0e8e197..37c5223 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -28,6 +28,7 @@ #include "../include/SfmlFixes.hpp" #include "../include/ResourceLoader.hpp" #include "../include/Utils.hpp" +#include "../include/Tabs.hpp" #include "../external/hash-library/sha256.h" #include @@ -50,10 +51,6 @@ static const sf::Color back_color(21, 25, 30); static const std::string fourchan_google_captcha_api_key = "6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc"; -static const float tab_text_size = std::floor(16.0f * QuickMedia::get_ui_scale()); -static const float tab_height = tab_text_size + std::floor(10.0f * QuickMedia::get_ui_scale()); -static const sf::Color tab_selected_color(55, 60, 68); -static const float tab_margin_x = std::floor(10.0f); static int FPS_IDLE = 2; static const double IDLE_TIMEOUT_SEC = 2.0; static const sf::Vector2i AVATAR_THUMBNAIL_SIZE(std::floor(32 * QuickMedia::get_ui_scale()), std::floor(32 * QuickMedia::get_ui_scale())); @@ -263,6 +260,7 @@ namespace QuickMedia { return search_page->submit(title, url, result_tabs); } void on_navigate_to_page(Body *body) override { + std::string selected_item_url = body->get_selected() ? body->get_selected()->url : ""; body->clear_items(); switch(history_type) { case HistoryType::YOUTUBE: @@ -273,6 +271,20 @@ namespace QuickMedia { break; } body->filter_search_fuzzy(search_bar->get_text()); + int item_to_revert_selection_to = get_body_item_by_url(body, selected_item_url); + if(item_to_revert_selection_to != -1) + body->set_selected_item(item_to_revert_selection_to, false); + } + + // Returns index to item or -1 if not found + int get_body_item_by_url(Body *body, const std::string &url) { + if(url.empty()) return -1; + for(size_t i = 0; i < body->items.size(); ++i) { + auto &body_item = body->items[i]; + if(body_item->url == url) + return i; + } + return -1; } private: Page *search_page; @@ -403,8 +415,7 @@ namespace QuickMedia { disp(nullptr), window_size(1280, 720), current_page(PageType::EXIT), - image_index(0), - tab_background(sf::Vector2f(1.0f, 1.0f), 10.0f, 10) + image_index(0) { } @@ -434,7 +445,7 @@ namespace QuickMedia { fprintf(stderr, " quickmedia launcher\n"); fprintf(stderr, " quickmedia --upscale-images-always manganelo\n"); fprintf(stderr, " echo -e \"hello\\nworld\" | quickmedia stdin\n"); - fprintf(stderr, " tabbed quickmedia launcher -e\n"); + fprintf(stderr, " tabbed -c -k quickmedia launcher -e\n"); } static bool is_manga_plugin(const char *plugin_name) { @@ -846,11 +857,11 @@ namespace QuickMedia { tabs.push_back(Tab{std::move(history_body), std::move(history_page), std::move(search_bar)}); } else if(strcmp(plugin_name, "manga") == 0) { auto manganelo = std::make_unique(this); - auto manganelos = std::make_unique(this, plugin_name, nullptr); + auto manganelos = std::make_unique(this, "manganelos", nullptr); add_manganelos_handlers(manganelos.get()); - auto mangatown = std::make_unique(this, plugin_name, "https://www.mangatown.com"); + auto mangatown = std::make_unique(this, "mangatown", "https://www.mangatown.com"); add_mangatown_handlers(mangatown.get()); - auto mangakatana = std::make_unique(this, plugin_name, "https://mangakatana.com", false); + auto mangakatana = std::make_unique(this, "mangakatana", "https://mangakatana.com", false); add_mangakatana_handlers(mangakatana.get()); std::vector pages; @@ -1148,12 +1159,12 @@ namespace QuickMedia { float body_padding_horizontal = 10.0f; float body_padding_vertical = std::floor(10.0f); float body_width = window_size.x - body_padding_horizontal * 2.0f; - if(body_width <= 480.0f) { + /*if(body_width <= 480.0f) { body_width = window_size.x; - body_padding_horizontal = 0.0f; - } + body_padding_horizontal = 10.0f; + }*/ - float tab_h = tab_height + std::floor(10.0f * get_ui_scale()); + float tab_h = Tabs::get_shade_height(); if(!search_bar) tab_h += std::floor(10.0f * get_ui_scale()); @@ -1214,40 +1225,12 @@ namespace QuickMedia { pipe_selected_text = text; } - void Program::page_loop_render(sf::RenderWindow &window, std::vector &tabs, int selected_tab, TabAssociatedData &tab_associated_data, const Json::Value *json_chapters) { + void Program::page_loop_render(sf::RenderWindow &window, std::vector &tabs, int selected_tab, TabAssociatedData &tab_associated_data, const Json::Value *json_chapters, Tabs &ui_tabs) { if(tabs[selected_tab].search_bar) tabs[selected_tab].search_bar->draw(window, false); - { - float shade_extra_height = 0.0f; - if(!tabs[selected_tab].search_bar) - shade_extra_height = std::floor(10.0f * get_ui_scale()); - - const float width_per_tab = window_size.x / tabs.size(); - tab_background.setSize(sf::Vector2f(std::floor(width_per_tab - tab_margin_x * 2.0f), tab_height)); - - float tab_vertical_offset = tabs[selected_tab].search_bar ? tabs[selected_tab].search_bar->getBottomWithoutShadow() : 0.0f; - tabs[selected_tab].body->draw(window, body_pos, body_size, *json_chapters); - const float tab_y = std::floor(tab_vertical_offset + tab_height * 0.5f - (tab_text_size + std::floor(5.0f * get_ui_scale())) * 0.5f) + shade_extra_height; - - tab_shade.setPosition(0.0f, std::floor(tab_vertical_offset)); - tab_shade.setSize(sf::Vector2f(window_size.x, shade_extra_height + tab_height + std::floor(10.0f * get_ui_scale()))); - window.draw(tab_shade); - - int i = 0; - // TODO: Dont show tabs if there is only one tab - for(Tab &tab : tabs) { - if(i == selected_tab) { - tab_background.setPosition(std::floor(i * width_per_tab + tab_margin_x), std::floor(tab_vertical_offset) + shade_extra_height); - window.draw(tab_background); - } - const float center = (i * width_per_tab) + (width_per_tab * 0.5f); - // TODO: Optimize. Only set once for each tab! - tab_text.setString(tab.page->get_title()); - tab_text.setPosition(std::floor(center - tab_text.getLocalBounds().width * 0.5f), std::floor(tab_y)); - window.draw(tab_text); - ++i; - } - } + float tab_vertical_offset = tabs[selected_tab].search_bar ? tabs[selected_tab].search_bar->getBottomWithoutShadow() : 0.0f; + ui_tabs.draw(window, sf::Vector2f(0.0f, tab_vertical_offset), window_size.x); + tabs[selected_tab].body->draw(window, body_pos, body_size, *json_chapters); if(tab_associated_data.fetching_next_page_running) window.draw(gradient_points, 4, sf::Quads); // Note: sf::Quads doesn't work with egl @@ -1285,11 +1268,25 @@ namespace QuickMedia { malloc_trim(0); idle = true; + bool loop_running = true; + bool redraw = true; + for(Tab &tab : tabs) { tab.body->thumbnail_max_size = tab.page->get_thumbnail_max_size(); tab.page->on_navigate_to_page(tab.body.get()); } + Tabs ui_tabs; + for(auto &tab : tabs) { + ui_tabs.add_tab(tab.page->get_title()); + } + ui_tabs.set_selected(start_tab_index); + + ui_tabs.on_change_tab = [&tabs, &redraw](int selected_tab) { + tabs[selected_tab].body->clear_cache(); + redraw = true; + }; + const Json::Value *json_chapters = &Json::Value::nullSingleton(); if(content_storage_json.isObject()) { const Json::Value &chapters_json = content_storage_json["chapters"]; @@ -1310,19 +1307,14 @@ namespace QuickMedia { double gradient_inc = 0.0; const float gradient_height = 5.0f; - tab_text = sf::Text("", *FontLoader::get_font(FontLoader::FontType::LATIN), tab_text_size); - int selected_tab = std::min(std::max(0, start_tab_index), (int)tabs.size() - 1); - - bool loop_running = true; - bool redraw = true; - auto window_size_u = window.getSize(); window_size.x = window_size_u.x; window_size.y = window_size_u.y; std::function submit_handler; - submit_handler = [this, &submit_handler, &after_submit_handler, &json_chapters, &tabs, &tab_associated_data, &selected_tab, &loop_running, &redraw](const std::string &search_text) { + submit_handler = [this, &submit_handler, &after_submit_handler, &json_chapters, &tabs, &tab_associated_data, &ui_tabs, &loop_running, &redraw](const std::string &search_text) { + const int selected_tab = ui_tabs.get_selected(); auto selected_item = tabs[selected_tab].body->get_selected_shared(); if(!selected_item && !tabs[selected_tab].page->allow_submit_no_selection()) return; @@ -1499,15 +1491,13 @@ namespace QuickMedia { } sf::Event event; - - tab_shade.setFillColor(sf::Color(33, 37, 44)); - tab_background.setFillColor(tab_selected_color); - sf::Clock frame_timer; while (window.isOpen() && loop_running) { sf::Int32 frame_time_ms = frame_timer.restart().asMilliseconds(); while (window.pollEvent(event)) { + const int selected_tab = ui_tabs.get_selected(); + if(tabs[selected_tab].body->on_event(window, event)) idle_active_handler(); else @@ -1525,6 +1515,8 @@ namespace QuickMedia { tabs[selected_tab].search_bar->on_event(event); } + ui_tabs.on_event(event); + if(event.type == sf::Event::Resized || event.type == sf::Event::GainedFocus) redraw = true; else if(event.type == sf::Event::KeyPressed) { @@ -1574,18 +1566,6 @@ namespace QuickMedia { tabs[selected_tab].body->select_first_item(); } else if(event.key.code == sf::Keyboard::Escape) { goto page_end; - } else if(event.key.code == sf::Keyboard::Left || (event.key.control && event.key.code == sf::Keyboard::H)) { - if(selected_tab > 0) { - tabs[selected_tab].body->clear_cache(); - --selected_tab; - redraw = true; - } - } else if(event.key.code == sf::Keyboard::Right || (event.key.control && event.key.code == sf::Keyboard::L)) { - if(selected_tab < (int)tabs.size() - 1) { - tabs[selected_tab].body->clear_cache(); - ++selected_tab; - redraw = true; - } } else if(event.key.code == sf::Keyboard::Tab) { if(tabs[selected_tab].search_bar) tabs[selected_tab].search_bar->set_to_autocomplete(); } else if(event.key.code == sf::Keyboard::Enter) { @@ -1614,6 +1594,8 @@ namespace QuickMedia { if(!loop_running || !window.isOpen()) break; + const int selected_tab = ui_tabs.get_selected(); + if(redraw) { redraw = false; if(tabs[selected_tab].search_bar) tabs[selected_tab].search_bar->onWindowResize(window_size); @@ -1722,7 +1704,7 @@ namespace QuickMedia { } window.clear(back_color); - page_loop_render(window, tabs, selected_tab, tab_associated_data[selected_tab], json_chapters); + page_loop_render(window, tabs, selected_tab, tab_associated_data[selected_tab], json_chapters, ui_tabs); window.display(); if(go_to_previous_page) { @@ -3215,10 +3197,10 @@ namespace QuickMedia { float body_padding_horizontal = 10.0f; float body_padding_vertical = std::floor(10.0f); float body_width = window_size.x - body_padding_horizontal * 2.0f; - if(body_width <= 480.0f) { + /*if(body_width <= 480.0f) { body_width = window_size.x; body_padding_horizontal = 0.0f; - } + }*/ comment_input_shade.setSize(sf::Vector2f(window_size.x, chat_input_height_full)); comment_input_shade.setPosition(0.0f, 0.0f); @@ -3420,7 +3402,6 @@ namespace QuickMedia { struct ChatTab { std::unique_ptr body; AsyncTask future; - sf::Text text; }; static const sf::Vector2i CHAT_MESSAGE_THUMBNAIL_MAX_SIZE(600, 337); @@ -3572,7 +3553,6 @@ namespace QuickMedia { pinned_tab.body->thumbnail_mask_shader = &circle_mask_shader; pinned_tab.body->attach_side = AttachSide::BOTTOM; pinned_tab.body->line_separator_color = sf::Color::Transparent; - pinned_tab.text = sf::Text("Pinned messages", *FontLoader::get_font(FontLoader::FontType::LATIN), tab_text_size); tabs.push_back(std::move(pinned_tab)); ChatTab messages_tab; @@ -3581,7 +3561,6 @@ namespace QuickMedia { messages_tab.body->thumbnail_mask_shader = &circle_mask_shader; messages_tab.body->attach_side = AttachSide::BOTTOM; messages_tab.body->line_separator_color = sf::Color::Transparent; - messages_tab.text = sf::Text("Messages", *FontLoader::get_font(FontLoader::FontType::LATIN), tab_text_size); tabs.push_back(std::move(messages_tab)); // ChatTab users_tab; @@ -3593,14 +3572,56 @@ namespace QuickMedia { // users_tab.text = sf::Text("Users", *FontLoader::get_font(FontLoader::FontType::LATIN), tab_text_size); // tabs.push_back(std::move(users_tab)); - const int PINNED_TAB_INDEX = 0; - const int MESSAGES_TAB_INDEX = 1; - //const int USERS_TAB_INDEX = 2; + Tabs ui_tabs(back_color); + const int PINNED_TAB_INDEX = ui_tabs.add_tab("Pinned messages (0)"); + const int MESSAGES_TAB_INDEX = ui_tabs.add_tab("Messages"); + ui_tabs.set_selected(MESSAGES_TAB_INDEX); matrix_chat_page->chat_body = tabs[MESSAGES_TAB_INDEX].body.get(); matrix_chat_page->messages_tab_visible = true; - int selected_tab = MESSAGES_TAB_INDEX; + bool redraw = true; + + sf::Clock read_marker_timer; + const sf::Int32 read_marker_timeout_ms_default = 3000; + sf::Int32 read_marker_timeout_ms = 0; + + AsyncTask set_read_marker_future; + bool setting_read_marker = false; + + sf::Clock start_typing_timer; + const double typing_timeout_seconds = 3.0; + bool typing = false; + + MessageQueue typing_state_queue; + auto typing_state_handler = [this, ¤t_room, &typing_state_queue]() { + while(true) { + std::optional state_opt = typing_state_queue.pop_wait(); + if(!state_opt) + break; + + bool state = state_opt.value(); + if(state) + matrix->on_start_typing(current_room); + else + matrix->on_stop_typing(current_room); + } + }; + std::thread typing_state_thread(typing_state_handler); + + ui_tabs.on_change_tab = [matrix_chat_page, &redraw, &typing, &typing_state_queue, &read_marker_timer, &tabs, MESSAGES_TAB_INDEX](int selected_tab) { + tabs[selected_tab].body->clear_cache(); + if(selected_tab == MESSAGES_TAB_INDEX) + matrix_chat_page->messages_tab_visible = true; + read_marker_timer.restart(); + redraw = true; + if(typing) { + fprintf(stderr, "Stopped typing\n"); + typing = false; + typing_state_queue.push(false); + } + }; + bool is_window_focused = window.hasFocus(); enum class ChatState { @@ -3633,14 +3654,6 @@ namespace QuickMedia { sf::Sprite room_avatar_sprite; auto room_avatar_thumbnail_data = std::make_shared(); - sf::Clock read_marker_timer; - const sf::Int32 read_marker_timeout_ms_default = 3000; - sf::Int32 read_marker_timeout_ms = 0; - - AsyncTask set_read_marker_future; - bool setting_read_marker = false; - - bool redraw = true; bool draw_room_list = show_room_side_panel; // TODO: What if these never end up referencing events? clean up automatically after a while? @@ -3700,7 +3713,7 @@ namespace QuickMedia { }; // TODO: Optimize find_body_item_by_event_id hash map? - auto modify_related_messages_in_current_room = [this, ¤t_room, &set_body_as_deleted, &unreferenced_events, &tabs](Messages &messages) { + auto modify_related_messages_in_current_room = [this, ¤t_room, &set_body_as_deleted, &unreferenced_events, &tabs, MESSAGES_TAB_INDEX](Messages &messages) { if(messages.empty()) return; @@ -3734,7 +3747,7 @@ namespace QuickMedia { std::vector> unresolved_reactions; // TODO: Optimize find_body_item_by_event_id hash map? - auto process_reactions = [&tabs, &unresolved_reactions, ¤t_room](Messages &messages) { + auto process_reactions = [&tabs, &unresolved_reactions, ¤t_room, MESSAGES_TAB_INDEX](Messages &messages) { if(messages.empty()) return; @@ -3779,7 +3792,7 @@ namespace QuickMedia { } }; - auto pinned_body_items_contains_event = [&tabs](const std::string &event_id) { + auto pinned_body_items_contains_event = [&tabs, PINNED_TAB_INDEX](const std::string &event_id) { for(auto &body_item : tabs[PINNED_TAB_INDEX].body->items) { if(static_cast(body_item->userdata)->event_id == event_id) return true; @@ -3787,7 +3800,7 @@ namespace QuickMedia { return false; }; - auto process_pinned_events = [&tabs, &pinned_body_items_contains_event](const std::optional> &pinned_events) { + auto process_pinned_events = [&tabs, &ui_tabs, &pinned_body_items_contains_event, PINNED_TAB_INDEX](const std::optional> &pinned_events) { if(!pinned_events) return; @@ -3821,7 +3834,7 @@ namespace QuickMedia { else tabs[PINNED_TAB_INDEX].body->set_selected_item(selected_before); - tabs[PINNED_TAB_INDEX].text.setString("Pinned messages (" + std::to_string(tabs[PINNED_TAB_INDEX].body->items.size()) + ")"); + ui_tabs.set_text(PINNED_TAB_INDEX, "Pinned messages (" + std::to_string(tabs[PINNED_TAB_INDEX].body->items.size()) + ")"); }; Body url_selection_body(this, loading_icon); @@ -3891,7 +3904,7 @@ namespace QuickMedia { }; std::thread post_thread(post_thread_handler); - auto message_set_replaced_by = [&tabs, &pending_sent_replies](std::shared_ptr message) { + auto message_set_replaced_by = [&tabs, &pending_sent_replies, MESSAGES_TAB_INDEX](std::shared_ptr message) { if(message->related_event_type == RelatedEventType::EDIT) { auto body_item = find_body_item_by_event_id(tabs[MESSAGES_TAB_INDEX].body->items.data(), tabs[MESSAGES_TAB_INDEX].body->items.size(), message->related_event_id); if(body_item) { @@ -3922,7 +3935,7 @@ namespace QuickMedia { } }; - auto upload_file = [this, &tabs, ¤t_room](const std::string &filepath) { + auto upload_file = [this, &tabs, ¤t_room, MESSAGES_TAB_INDEX](const std::string &filepath) { TaskResult post_file_result = run_task_with_loading_screen([this, ¤t_room, filepath]() { std::string event_id_response; std::string err_msg; @@ -3942,11 +3955,12 @@ namespace QuickMedia { bool frame_skip_text_entry = false; - chat_input.on_submit_callback = [this, &frame_skip_text_entry, &tabs, &me, &chat_input, &selected_tab, ¤t_room, &new_page, &chat_state, &pending_sent_replies, ¤tly_operating_on_item, &post_task_queue, &process_reactions](std::string text) mutable { + chat_input.on_submit_callback = [this, &frame_skip_text_entry, &tabs, &me, &chat_input, &ui_tabs, MESSAGES_TAB_INDEX, ¤t_room, &new_page, &chat_state, &pending_sent_replies, ¤tly_operating_on_item, &post_task_queue, &process_reactions](std::string text) mutable { if(!current_room) return false; frame_skip_text_entry = true; + const int selected_tab = ui_tabs.get_selected(); if(selected_tab == MESSAGES_TAB_INDEX) { if(text.empty()) @@ -4126,7 +4140,7 @@ namespace QuickMedia { int fetch_message_tab = -1; // TODO: How about instead fetching all messages we have, not only the visible ones? also fetch with multiple threads. - tabs[PINNED_TAB_INDEX].body->body_item_render_callback = [this, ¤t_room, &me, &fetch_message_future, &tabs, &fetch_body_item, &fetch_message_tab](BodyItem *body_item) { + tabs[PINNED_TAB_INDEX].body->body_item_render_callback = [this, ¤t_room, &me, &fetch_message_future, &tabs, &fetch_body_item, &fetch_message_tab, PINNED_TAB_INDEX, MESSAGES_TAB_INDEX](BodyItem *body_item) { if(fetch_message_future.valid()) return; @@ -4200,7 +4214,7 @@ namespace QuickMedia { bool remove_unread_marker = false; // TODO: How about instead fetching all messages we have, not only the visible ones? also fetch with multiple threads. - tabs[MESSAGES_TAB_INDEX].body->body_item_render_callback = [this, ¤t_room, &me, &remove_unread_marker, &fetch_message_future, &tabs, &is_window_focused, &chat_state, &setting_read_marker, &read_marker_timer, &read_marker_timeout_ms, &set_read_marker_future, &fetch_body_item, &fetch_message_tab](BodyItem *body_item) { + tabs[MESSAGES_TAB_INDEX].body->body_item_render_callback = [this, ¤t_room, &me, &remove_unread_marker, &fetch_message_future, &tabs, &is_window_focused, &chat_state, &setting_read_marker, &read_marker_timer, &read_marker_timeout_ms, &set_read_marker_future, &fetch_body_item, &fetch_message_tab, MESSAGES_TAB_INDEX](BodyItem *body_item) { Message *message = static_cast(body_item->userdata); if(!message) return; @@ -4281,17 +4295,10 @@ namespace QuickMedia { return false; }; - const float tab_spacer_height = 0.0f; sf::Vector2f body_pos; sf::Vector2f body_size; sf::Event event; - sf::RectangleShape tab_shade; - tab_shade.setFillColor(sf::Color(33, 37, 44)); - - sf::RoundedRectangleShape tab_background(sf::Vector2f(1.0f, 1.0f), 10.0f, 10); - tab_background.setFillColor(tab_selected_color); - const float gradient_height = 5.0f; sf::Vertex gradient_points[4]; double gradient_inc = 0; @@ -4300,7 +4307,7 @@ namespace QuickMedia { bool fetched_enough_messages = false; - auto fetch_more_previous_messages_if_needed = [this, &tabs, ¤t_room, &fetched_enough_messages, &previous_messages_future]() { + auto fetch_more_previous_messages_if_needed = [this, &tabs, ¤t_room, &fetched_enough_messages, &previous_messages_future, MESSAGES_TAB_INDEX]() { if(!fetched_enough_messages && !previous_messages_future.valid()) { if(tabs[MESSAGES_TAB_INDEX].body->items.size() < 30) { previous_messages_future = AsyncTask([this, ¤t_room]() { @@ -4328,28 +4335,8 @@ namespace QuickMedia { sf::RectangleShape chat_input_shade; chat_input_shade.setFillColor(sf::Color(33, 37, 44)); - sf::Clock start_typing_timer; - const double typing_timeout_seconds = 3.0; - bool typing = false; - float tab_vertical_offset = 0.0f; - MessageQueue typing_state_queue; - auto typing_state_handler = [this, ¤t_room, &typing_state_queue]() { - while(true) { - std::optional state_opt = typing_state_queue.pop_wait(); - if(!state_opt) - break; - - bool state = state_opt.value(); - if(state) - matrix->on_start_typing(current_room); - else - matrix->on_stop_typing(current_room); - } - }; - std::thread typing_state_thread(typing_state_handler); - sf::Clock frame_timer; float prev_chat_height = chat_input.get_height(); @@ -4400,10 +4387,11 @@ namespace QuickMedia { } }; - auto add_new_messages_to_current_room = [&me, &tabs, &selected_tab, ¤t_room](Messages &messages) { + auto add_new_messages_to_current_room = [&me, &tabs, &ui_tabs, ¤t_room, MESSAGES_TAB_INDEX](Messages &messages) { if(messages.empty()) return; + const int selected_tab = ui_tabs.get_selected(); int num_items = tabs[MESSAGES_TAB_INDEX].body->items.size(); bool scroll_to_end = num_items == 0; if(selected_tab == MESSAGES_TAB_INDEX && (tabs[MESSAGES_TAB_INDEX].body->is_selected_item_last_visible_item() || !tabs[MESSAGES_TAB_INDEX].body->get_selected())) @@ -4428,10 +4416,11 @@ namespace QuickMedia { } }; - auto display_url_or_image = [this, matrix_chat_page, &selected_tab, &redraw, &video_page, &launch_url, &chat_state, &url_selection_body](BodyItem *selected) { + auto display_url_or_image = [this, matrix_chat_page, &ui_tabs, &redraw, &video_page, &launch_url, &chat_state, &url_selection_body, PINNED_TAB_INDEX, MESSAGES_TAB_INDEX](BodyItem *selected) { if(!selected) return false; + const int selected_tab = ui_tabs.get_selected(); Message *selected_item_message = nullptr; if(selected_tab == MESSAGES_TAB_INDEX) { selected_item_message = static_cast(selected->userdata); @@ -4480,7 +4469,7 @@ namespace QuickMedia { return false; }; - auto update_pinned_messages_author = [&tabs, ¤t_room](const std::shared_ptr &user) { + auto update_pinned_messages_author = [&tabs, ¤t_room, PINNED_TAB_INDEX](const std::shared_ptr &user) { fprintf(stderr, "updated pinned messages author for user: %s\n", user->user_id.c_str()); for(auto &pinned_body_item : tabs[PINNED_TAB_INDEX].body->items) { Message *message = static_cast(pinned_body_item->userdata)->message; @@ -4492,7 +4481,7 @@ namespace QuickMedia { } }; - auto update_messages_author = [&tabs, ¤t_room](const std::shared_ptr &user) { + auto update_messages_author = [&tabs, ¤t_room, MESSAGES_TAB_INDEX](const std::shared_ptr &user) { fprintf(stderr, "updated messages author for user: %s\n", user->user_id.c_str()); for(auto &message_body_items : tabs[MESSAGES_TAB_INDEX].body->items) { Message *message = static_cast(message_body_items->userdata); @@ -4504,7 +4493,7 @@ namespace QuickMedia { }; // TODO: Optimize - auto update_pinned_messages_authors = [&tabs, ¤t_room]() { + auto update_pinned_messages_authors = [&tabs, ¤t_room, PINNED_TAB_INDEX]() { fprintf(stderr, "updated pinned messages author for all users in room: %s\n", current_room->id.c_str()); for(auto &pinned_body_item : tabs[PINNED_TAB_INDEX].body->items) { Message *message = static_cast(pinned_body_item->userdata)->message; @@ -4517,7 +4506,7 @@ namespace QuickMedia { }; // TODO: Optimize - auto update_messages_authors = [&tabs, ¤t_room]() { + auto update_messages_authors = [&tabs, ¤t_room, MESSAGES_TAB_INDEX]() { fprintf(stderr, "updated messages author for all users in room: %s\n", current_room->id.c_str()); for(auto &message_body_items : tabs[MESSAGES_TAB_INDEX].body->items) { Message *message = static_cast(message_body_items->userdata); @@ -4528,7 +4517,7 @@ namespace QuickMedia { } }; - auto cleanup_tasks = [&set_read_marker_future, &fetch_message_future, &fetch_users_future, &typing_state_queue, &typing_state_thread, &post_task_queue, &provisional_message_queue, &fetched_messages_set, &sent_messages, &pending_sent_replies, &post_thread, &tabs]() { + auto cleanup_tasks = [&set_read_marker_future, &fetch_message_future, &fetch_users_future, &typing_state_queue, &typing_state_thread, &post_task_queue, &provisional_message_queue, &fetched_messages_set, &sent_messages, &pending_sent_replies, &post_thread, &tabs, PINNED_TAB_INDEX]() { set_read_marker_future.cancel(); fetch_message_future.cancel(); fetch_users_future.cancel(); @@ -4574,16 +4563,20 @@ namespace QuickMedia { } float tab_shade_height = 0.0f; + SyncData sync_data; room_tabs[room_selected_tab].body->body_item_select_callback = [&move_room](BodyItem *body_item) { move_room = true; }; - SyncData sync_data; - while (current_page == PageType::CHAT && window.isOpen() && !move_room) { sf::Int32 frame_time_ms = frame_timer.restart().asMilliseconds(); while (window.pollEvent(event)) { + const int selected_tab = ui_tabs.get_selected(); + + if(chat_state == ChatState::NAVIGATING) + ui_tabs.on_event(event); + if(chat_state == ChatState::URL_SELECTION) { if(url_selection_body.on_event(window, event)) idle_active_handler(); @@ -4680,30 +4673,6 @@ namespace QuickMedia { tabs[selected_tab].body.get()->select_next_page(); } else if(event.key.code == sf::Keyboard::End) { tabs[selected_tab].body.get()->select_last_item(); - } else if((event.key.code == sf::Keyboard::Left || (event.key.control && event.key.code == sf::Keyboard::H)) && selected_tab > 0) { - tabs[selected_tab].body->clear_cache(); - --selected_tab; - if(selected_tab == MESSAGES_TAB_INDEX) - matrix_chat_page->messages_tab_visible = true; - read_marker_timer.restart(); - redraw = true; - if(typing && current_room) { - fprintf(stderr, "Stopped typing\n"); - typing = false; - typing_state_queue.push(false); - } - } else if((event.key.code == sf::Keyboard::Right || (event.key.control && event.key.code == sf::Keyboard::L)) && selected_tab < (int)tabs.size() - 1) { - tabs[selected_tab].body->clear_cache(); - ++selected_tab; - if(selected_tab == MESSAGES_TAB_INDEX) - matrix_chat_page->messages_tab_visible = true; - read_marker_timer.restart(); - redraw = true; - if(typing && current_room) { - fprintf(stderr, "Stopped typing\n"); - typing = false; - typing_state_queue.push(false); - } } else if(event.key.code == sf::Keyboard::Escape) { goto chat_page_end; } @@ -4960,6 +4929,8 @@ namespace QuickMedia { } } + const int selected_tab = ui_tabs.get_selected(); + float room_name_padding_y = 0.0f; if(selected_tab == MESSAGES_TAB_INDEX || selected_tab == PINNED_TAB_INDEX) room_name_padding_y = room_name_total_height; @@ -4984,15 +4955,15 @@ namespace QuickMedia { tab_vertical_offset = std::floor(10.0f * get_ui_scale()); } - tab_shade_height = tab_spacer_height + std::floor(tab_vertical_offset) + tab_height + room_name_padding_y + padding_bottom; + tab_shade_height = std::floor(tab_vertical_offset) + Tabs::get_height() + room_name_padding_y + padding_bottom; float body_padding_horizontal = 10.0f; float body_padding_vertical = std::floor(10.0f); float body_width = window_size.x - body_padding_horizontal * 2.0f; - if(body_width <= 480.0f) { + /*if(body_width <= 480.0f) { body_width = window_size.x; body_padding_horizontal = 0.0f; - } + }*/ this->body_pos = sf::Vector2f(0.0f, tab_shade_height); if(window_size.x > 900.0f && show_room_side_panel) { @@ -5113,18 +5084,12 @@ namespace QuickMedia { fetch_message_tab = -1; } - //chat_input.update(); - window.clear(back_color); - - const float width_per_tab = body_size.x / tabs.size(); - tab_background.setSize(sf::Vector2f(std::floor(width_per_tab - tab_margin_x * 2.0f), tab_height)); if(chat_state == ChatState::URL_SELECTION) url_selection_body.draw(window, body_pos, body_size); else tabs[selected_tab].body->draw(window, body_pos, body_size); - const float tab_y = tab_spacer_height + std::floor(tab_vertical_offset + tab_height * 0.5f - (tab_text_size + 5.0f) * 0.5f) + room_name_padding_y; //tab_shade.setSize(sf::Vector2f(window_size.x, tab_shade_height)); //window.draw(tab_shade); @@ -5152,21 +5117,12 @@ namespace QuickMedia { window.draw(room_list_background); window.draw(room_label); const float padding_x = std::floor(10.0f * get_ui_scale()); + const float tab_y = std::floor(tab_vertical_offset) + room_name_padding_y; room_tabs[room_selected_tab].body->draw(window, sf::Vector2f(padding_x, tab_y), sf::Vector2f(this->body_size.x - padding_x * 2.0f, window_size.y - tab_y), Json::Value::nullSingleton()); glDisable(GL_SCISSOR_TEST); } - int i = 0; - for(ChatTab &tab : tabs) { - if(i == selected_tab) { - tab_background.setPosition(std::floor(body_pos.x + i * width_per_tab + tab_margin_x), tab_spacer_height + std::floor(tab_vertical_offset) + room_name_padding_y); - window.draw(tab_background); - } - const float center = body_pos.x + (i * width_per_tab) + (width_per_tab * 0.5f); - tab.text.setPosition(std::floor(center - tab.text.getLocalBounds().width * 0.5f), std::floor(tab_y)); - window.draw(tab.text); - ++i; - } + ui_tabs.draw(window, sf::Vector2f(body_pos.x, std::floor(tab_vertical_offset) + room_name_padding_y), body_size.x); // TODO: Have one for each room. Also add bottom one? for fetching new messages (currently not implemented, is it needed?) if(previous_messages_future.valid() && selected_tab == MESSAGES_TAB_INDEX) { diff --git a/src/SearchBar.cpp b/src/SearchBar.cpp index 63515bd..fa119a6 100644 --- a/src/SearchBar.cpp +++ b/src/SearchBar.cpp @@ -167,10 +167,13 @@ namespace QuickMedia { sf::Vector2f texture_size_f(texture_size.x, texture_size.y); sf::Vector2f new_size = wrap_to_size(texture_size_f, sf::Vector2f(200.0f, one_line_height)); plugin_logo_sprite.setScale(get_ratio(texture_size_f, new_size)); - plugin_logo_sprite.setPosition(25.0f, padding_top + vertical_pos + rect_height * 0.5f - plugin_logo_sprite.getTexture()->getSize().y * plugin_logo_sprite.getScale().y * 0.5f); - offset_x = 25.0f + new_size.x + 25.0f; + plugin_logo_sprite.setPosition(10.0f, padding_top + vertical_pos + rect_height * 0.5f - plugin_logo_sprite.getTexture()->getSize().y * plugin_logo_sprite.getScale().y * 0.5f); + offset_x = 10.0f + new_size.x + 10.0f; + } else { + offset_x = 10.0f; } - const float width = std::floor(window_size.x - offset_x - 25.0f); + + const float width = std::floor(window_size.x - offset_x - 10.0f); background.setSize(sf::Vector2f(width, rect_height)); shade.setSize(sf::Vector2f(window_size.x, padding_top + rect_height + padding_bottom)); diff --git a/src/Tabs.cpp b/src/Tabs.cpp new file mode 100644 index 0000000..464ba4e --- /dev/null +++ b/src/Tabs.cpp @@ -0,0 +1,200 @@ +#include "../include/Tabs.hpp" +#include "../include/ResourceLoader.hpp" +#include "../include/Utils.hpp" +#include +#include +#include +#include + +namespace QuickMedia { + static const float tab_text_size = std::floor(16.0f * QuickMedia::get_ui_scale()); + static const float tab_height = tab_text_size + std::floor(10.0f * QuickMedia::get_ui_scale()); + static const sf::Color tab_selected_color(55, 60, 68); + static const float tab_min_width = 250.0f; + static const float tab_margin_x = 10.0f; + + // static + float Tabs::get_height() { + return tab_height; + } + + // static + float Tabs::get_shade_height() { + return tab_height + std::floor(10.0f * get_ui_scale()); + } + + Tabs::Tabs(sf::Color shade_color) : background(sf::Vector2f(1.0f, 1.0f), 10.0f, 10), shade_color(shade_color) { + background.setFillColor(tab_selected_color); + shade.setFillColor(shade_color); + } + + int Tabs::add_tab(const std::string &title) { + tab_texts.push_back(sf::Text(title, *FontLoader::get_font(FontLoader::FontType::LATIN), tab_text_size)); + return tab_texts.size() - 1; + } + + void Tabs::on_event(sf::Event &event) { + if(event.type == sf::Event::KeyPressed) { + if(event.key.code == sf::Keyboard::Left || (event.key.control && event.key.code == sf::Keyboard::H)) { + if(selected_tab > 0) { + --selected_tab; + float scroll_fixed = scroll + (tab_offset * width_per_tab); + if(scroll_fixed + tab_index_to_x_offset(selected_tab) + tab_background_width < 0.0f) + tab_offset++; + + if(on_change_tab) + on_change_tab(selected_tab); + } + } else if(event.key.code == sf::Keyboard::Right || (event.key.control && event.key.code == sf::Keyboard::L)) { + if(selected_tab < (int)tab_texts.size() - 1) { + ++selected_tab; + float scroll_fixed = scroll + (tab_offset * width_per_tab); + if(scroll_fixed + tab_index_to_x_offset(selected_tab) + tab_background_width > container_width) + tab_offset--; + + if(on_change_tab) + on_change_tab(selected_tab); + } + } + } + } + + void Tabs::draw(sf::RenderWindow &window, sf::Vector2f pos, float width) { + if(width - tab_margin_x < 0.0f || tab_texts.empty()) return; + + auto window_size = window.getSize(); + container_width = width; + + const int num_visible_tabs = std::min((int)tab_texts.size(), std::max(1, (int)(width / tab_min_width))); + width_per_tab = std::floor(width / num_visible_tabs); + const float tab_text_y = std::floor(pos.y + tab_height*0.5f - (tab_text_size + 5.0f*get_ui_scale())*0.5f); + tab_background_width = std::floor(width_per_tab - tab_margin_x*2.0f); + background.setSize(sf::Vector2f(tab_background_width, tab_height)); + + shade.setSize(sf::Vector2f(width, get_shade_height())); + shade.setPosition(std::floor(pos.x), std::floor(pos.y)); + window.draw(shade); + + float scroll_fixed = scroll + (tab_offset * width_per_tab); + + float overflow_last = (scroll_fixed + tab_index_to_x_offset(tab_texts.size() - 1) + tab_background_width) - (width - tab_margin_x); + if(overflow_last < 0.0f) + scroll_fixed -= overflow_last; + + float overflow = (scroll_fixed + tab_index_to_x_offset(selected_tab) + tab_background_width) - (width - tab_margin_x); + float underflow = scroll_fixed + tab_index_to_x_offset(selected_tab); + if(overflow > 0.0f) + scroll_fixed -= overflow; + else if(underflow < 0.0f) + scroll_fixed -= underflow; + + bool tabs_cutoff_left = false; + bool tabs_cutoff_right = false; + const auto start_pos = pos; + + pos.x += scroll_fixed; + for(size_t i = 0; i < tab_texts.size(); ++i) { + const int index = i; + const float background_pos_x = std::floor(pos.x + tab_index_to_x_offset(i)); + if(background_pos_x - start_pos.x >= width - tab_margin_x) { + tabs_cutoff_right = true; + break; + } else if(background_pos_x + tab_background_width - start_pos.x <= tab_margin_x) { + tabs_cutoff_left = true; + continue; + } + + if((int)index == selected_tab) { + background.setPosition(background_pos_x, std::floor(pos.y)); + window.draw(background); + } + + sf::Text &tab_text = tab_texts[index]; + float text_pos_x = std::floor(pos.x + i*width_per_tab + width_per_tab*0.5f - tab_text.getLocalBounds().width*0.5f); + text_pos_x = std::max(text_pos_x, background_pos_x); + tab_text.setPosition(text_pos_x, tab_text_y); + glEnable(GL_SCISSOR_TEST); + glScissor(text_pos_x, (int)window_size.y - (int)tab_text_y - (int)tab_height, tab_background_width, tab_height); + window.draw(tab_text); + glDisable(GL_SCISSOR_TEST); + } + + float lw = std::floor(25.0f * get_ui_scale()); + float lh = background.getSize().y; + + float line_offset_y = std::floor(lw * 0.35f); + + if(tabs_cutoff_left) { + sf::Vertex gradient_points[4]; + gradient_points[0].position = sf::Vector2f(start_pos.x + tab_margin_x, start_pos.y); + gradient_points[1].position = sf::Vector2f(start_pos.x + tab_margin_x + lw, start_pos.y); + gradient_points[2].position = sf::Vector2f(start_pos.x + tab_margin_x + lw, start_pos.y + lh); + gradient_points[3].position = sf::Vector2f(start_pos.x + tab_margin_x, start_pos.y + lh); + + gradient_points[0].color = shade_color; + gradient_points[1].color = sf::Color(shade_color.r, shade_color.g, shade_color.b, 10); + gradient_points[2].color = sf::Color(shade_color.r, shade_color.g, shade_color.b, 10); + gradient_points[3].color = shade_color; + + window.draw(gradient_points, 4, sf::Quads); + + sf::RectangleShape line(sf::Vector2f(std::floor(10.0f * get_ui_scale()), std::floor(2.0f * get_ui_scale()))); + line.setOrigin(line.getSize().x * 0.5f, line.getSize().y * 0.5f); + + line.rotate(-45.0f); + line.setPosition(std::floor(start_pos.x + line.getLocalBounds().width), std::floor(pos.y + background.getSize().y * 0.5f - lh * 0.5f + line_offset_y)); + window.draw(line); + + line.rotate(-90.0f); + line.setPosition(std::floor(start_pos.x + line.getLocalBounds().width), std::floor(pos.y + background.getSize().y * 0.5f - lh * 0.5f + line_offset_y + std::floor(7.0f * get_ui_scale()))); + window.draw(line); + } + + if(tabs_cutoff_right) { + sf::Vertex gradient_points[4]; + gradient_points[0].position = sf::Vector2f(start_pos.x + width - lw - tab_margin_x, start_pos.y); + gradient_points[1].position = sf::Vector2f(start_pos.x + width, start_pos.y); + gradient_points[2].position = sf::Vector2f(start_pos.x + width, start_pos.y + lh); + gradient_points[3].position = sf::Vector2f(start_pos.x + width - lw - tab_margin_x, start_pos.y + lh); + + gradient_points[0].color = sf::Color(shade_color.r, shade_color.g, shade_color.b, 10); + gradient_points[1].color = shade_color; + gradient_points[2].color = shade_color; + gradient_points[3].color = sf::Color(shade_color.r, shade_color.g, shade_color.b, 10); + + window.draw(gradient_points, 4, sf::Quads); + + sf::RectangleShape line(sf::Vector2f(std::floor(10.0f * get_ui_scale()), std::floor(2.0f * get_ui_scale()))); + line.setOrigin(line.getSize().x * 0.5f, line.getSize().y * 0.5f); + + line.rotate(45.0f); + line.setPosition(std::floor(start_pos.x + width - lw*0.75f + line.getLocalBounds().width), std::floor(pos.y + background.getSize().y * 0.5f - lh * 0.5f + line_offset_y)); + window.draw(line); + + line.rotate(-90.0f); + line.setPosition(std::floor(start_pos.x + width - lw*0.75f + line.getLocalBounds().width), std::floor(pos.y + background.getSize().y * 0.5f - lh * 0.5f + line_offset_y + std::floor(7.0f * get_ui_scale()))); + window.draw(line); + } + } + + void Tabs::set_text(int index, const std::string &text) { + if(index < 0 || index >= (int)tab_texts.size()) return; + tab_texts[index].setString(sf::String::fromUtf8(text.begin(), text.end())); + } + + void Tabs::set_selected(int index) { + if(tab_texts.empty()) { + selected_tab = 0; + } else { + selected_tab = std::min(std::max(index, 0), (int)tab_texts.size() - 1); + } + } + + int Tabs::get_selected() const { + return selected_tab; + } + + float Tabs::tab_index_to_x_offset(int index) { + return std::floor(index*width_per_tab + tab_margin_x); + } +} \ No newline at end of file diff --git a/src/plugins/MangaCombined.cpp b/src/plugins/MangaCombined.cpp index 5b80872..e6b8028 100644 --- a/src/plugins/MangaCombined.cpp +++ b/src/plugins/MangaCombined.cpp @@ -40,7 +40,7 @@ namespace QuickMedia { continue; auto title_item = BodyItem::create(""); - title_item->set_author("======================== " + search_thread.first->title + " ========================"); + title_item->set_author("------------------------ " + search_thread.first->title + " ------------------------"); result_items.push_back(std::move(title_item)); for(auto &new_body_item : search_page_body_items) { @@ -65,7 +65,7 @@ namespace QuickMedia { for(auto &f : plugin_finished_state) { if(!f.second) { auto title_item = BodyItem::create(""); - title_item->set_author("======================== " + f.first->title + " timed out ========================"); + title_item->set_author("------------------------ " + f.first->title + " timed out ------------------------"); result_items.push_back(std::move(title_item)); } } diff --git a/src/plugins/MangaGeneric.cpp b/src/plugins/MangaGeneric.cpp index 8a17040..24472e5 100644 --- a/src/plugins/MangaGeneric.cpp +++ b/src/plugins/MangaGeneric.cpp @@ -87,8 +87,9 @@ namespace QuickMedia { if(merge_userdata->type == MergeType::THUMBNAIL) { (*body_item_image_context.body_items)[body_item_image_context.index]->thumbnail_url = strip(field_value); } else if(merge_userdata->type == MergeType::DESCRIPTION) { - std::string uploaded_date = strip(field_value); - (*body_item_image_context.body_items)[body_item_image_context.index]->set_description(merge_userdata->desc_prefix ? merge_userdata->desc_prefix : "" + uploaded_date); + std::string field_stripped = strip(field_value); + const char *prefix = merge_userdata->desc_prefix ? merge_userdata->desc_prefix : ""; + (*body_item_image_context.body_items)[body_item_image_context.index]->set_description(prefix + field_stripped); (*body_item_image_context.body_items)[body_item_image_context.index]->set_description_color(sf::Color(179, 179, 179)); } body_item_image_context.index++; diff --git a/src/plugins/Manganelo.cpp b/src/plugins/Manganelo.cpp index 6d0447a..f8f604d 100644 --- a/src/plugins/Manganelo.cpp +++ b/src/plugins/Manganelo.cpp @@ -125,7 +125,7 @@ namespace QuickMedia { auto item = BodyItem::create(strip(name_str)); item->url = "https://manganelo.com/manga/" + url_param_encode(nameunsigned.asString()); if(lastchapter.isString() && lastchapter.asCString()[0] != '\0') { - item->set_description("Last chapter: " + lastchapter.asString()); + item->set_description("Latest chapter: " + lastchapter.asString()); item->set_description_color(sf::Color(179, 179, 179)); } Json::Value image = child.get("image", ""); -- cgit v1.2.3