diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Body.cpp | 699 | ||||
-rw-r--r-- | src/Entry.cpp | 2 | ||||
-rw-r--r-- | src/QuickMedia.cpp | 60 | ||||
-rw-r--r-- | src/RoundedRectangle.cpp | 13 | ||||
-rw-r--r-- | src/SearchBar.cpp | 5 | ||||
-rw-r--r-- | src/Tabs.cpp | 92 | ||||
-rw-r--r-- | src/plugins/Fourchan.cpp | 4 | ||||
-rw-r--r-- | src/plugins/Page.cpp | 4 | ||||
-rw-r--r-- | src/plugins/Soundcloud.cpp | 6 | ||||
-rw-r--r-- | src/plugins/Youtube.cpp | 2 |
10 files changed, 585 insertions, 302 deletions
diff --git a/src/Body.cpp b/src/Body.cpp index ffd9337..c0d2a74 100644 --- a/src/Body.cpp +++ b/src/Body.cpp @@ -29,6 +29,16 @@ struct BodySpacing { float embedded_item_font_size = 0.0f; }; +static const int card_width = 250.0f * QuickMedia::get_ui_scale(); +static const int card_height = 350.0f * QuickMedia::get_ui_scale(); + +static const int min_column_spacing = 10 * QuickMedia::get_ui_scale(); +static const int card_padding_x = 20 * QuickMedia::get_ui_scale(); +static const int card_padding_y = 20 * QuickMedia::get_ui_scale(); +static const int card_image_text_padding = 10 * QuickMedia::get_ui_scale(); +static const sf::Vector2i card_max_image_size(card_width - card_padding_x * 2, (card_height - card_padding_y * 2) / 2); +static const int num_columns_switch_to_list = 1; + static BodySpacing body_spacing[2]; static bool themes_initialized = false; @@ -76,6 +86,10 @@ namespace QuickMedia { themes_initialized = true; } + static sf::Vector2f to_vec2f(const sf::Vector2i &vec) { + return sf::Vector2f(vec.x, vec.y); + } + BodyItem::BodyItem(std::string _title) : visible(true), dirty(false), @@ -162,7 +176,7 @@ namespace QuickMedia { num_visible_items(0), top_cut_off(false), bottom_cut_off(false), - item_background(sf::Vector2f(1.0f, 1.0f), 10.0f, sf::Color(55, 60, 68), rounded_rectangle_shader), + item_background(sf::Vector2f(1.0f, 1.0f), 10.0f, sf::Color(60, 65, 73), rounded_rectangle_shader), reaction_background(sf::Vector2f(1.0f, 1.0f), 10.0f, sf::Color(33, 37, 44), rounded_rectangle_shader), rounded_rectangle_mask_shader(rounded_rectangle_mask_shader) { @@ -188,10 +202,10 @@ namespace QuickMedia { // TODO: For plugins with different sized body items this can be weird, because after scrolling down thumbnails could load and they could move items up/down until we see items we haven't seen bool Body::select_previous_page() { if(!selected_item_fits_in_body) - return select_previous_item(false); + return select_previous_item(false, true); for(int i = 0; i < num_visible_items - 1; ++i) { - if(!select_previous_item(false)) + if(!select_previous_item(false, true)) return false; } return true; @@ -201,44 +215,62 @@ namespace QuickMedia { // TODO: For plugins with different sized body items this can be weird, because after scrolling down thumbnails could load and they could move items up/down until we see items we haven't seen bool Body::select_next_page() { if(!selected_item_fits_in_body) - return select_next_item(false); + return select_next_item(false, true); for(int i = 0; i < num_visible_items - 1; ++i) { - if(!select_next_item(false)) + if(!select_next_item(false, true)) return false; } return true; } - bool Body::select_previous_item(bool scroll_page_if_large_item) { + bool Body::select_previous_item(bool scroll_page_if_large_item, bool ignore_columns) { if(items.empty()) return false; - if(scroll_page_if_large_item && !selected_item_fits_in_body && !selected_line_top_visible) { + if(num_columns == 1 && scroll_page_if_large_item && !selected_item_fits_in_body && !selected_line_top_visible) { selected_scrolled += 128.0f; return true; } - const int new_selected_item = get_previous_visible_item(selected_item); - if(new_selected_item == -1) - return false; + int new_selected_item = selected_item; + for(int i = 0; i < (ignore_columns ? 1 : num_columns); ++i) { + const int prev = new_selected_item; + new_selected_item = get_previous_visible_item(new_selected_item); + if(new_selected_item == -1) { + if(i == 0) + return false; + + new_selected_item = prev; + break; + } + } selected_item = new_selected_item; return true; } - bool Body::select_next_item(bool scroll_page_if_large_item) { + bool Body::select_next_item(bool scroll_page_if_large_item, bool ignore_columns) { if(items.empty()) return false; - if(scroll_page_if_large_item && !selected_item_fits_in_body && !selected_line_bottom_visible) { + if(num_columns == 1 && scroll_page_if_large_item && !selected_item_fits_in_body && !selected_line_bottom_visible) { selected_scrolled -= 128.0f; return true; } - const int new_selected_item = get_next_visible_item(selected_item); - if(new_selected_item == -1) - return false; + int new_selected_item = selected_item; + for(int i = 0; i < (ignore_columns ? 1 : num_columns); ++i) { + const int prev = new_selected_item; + new_selected_item = get_next_visible_item(new_selected_item); + if(new_selected_item == -1) { + if(i == 0) + return false; + + new_selected_item = prev; + break; + } + } selected_item = new_selected_item; return true; @@ -388,6 +420,8 @@ namespace QuickMedia { bool Body::on_event(const sf::RenderWindow &window, const sf::Event &event, bool keyboard_navigation) { if(keyboard_navigation && event.type == sf::Event::KeyPressed && !event.key.alt) { + const bool rendering_card_view = card_view && card_view_enabled; + if(event.key.code == sf::Keyboard::Up || (event.key.control && event.key.code == sf::Keyboard::K)) { render_selected_item_bg = true; bool top_reached = select_previous_item(true); @@ -400,6 +434,24 @@ namespace QuickMedia { if(!bottom_reached && on_bottom_reached) on_bottom_reached(); return true; + } else if(rendering_card_view && selected_column > 0 && (event.key.code == sf::Keyboard::Left || (event.key.control && event.key.code == sf::Keyboard::H))) { + render_selected_item_bg = true; + const int new_selected_item = get_previous_visible_item(selected_item); + if(new_selected_item != -1) { + selected_item = new_selected_item; + } else if(on_top_reached) { + on_top_reached(); + } + return true; + } else if(rendering_card_view && selected_column + 1 < num_columns && (event.key.code == sf::Keyboard::Right || (event.key.control && event.key.code == sf::Keyboard::L))) { + render_selected_item_bg = true; + const int new_selected_item = get_next_visible_item(selected_item); + if(new_selected_item != -1) { + selected_item = new_selected_item; + } else if(on_bottom_reached) { + on_bottom_reached(); + } + return true; } else if(event.key.code == sf::Keyboard::Home) { render_selected_item_bg = true; select_first_item(false); @@ -468,12 +520,14 @@ namespace QuickMedia { if(!show_drop_shadow) return; + const sf::Color color(21, 25, 30); const float height = 5.0f; + sf::Vertex gradient_points[4]; - gradient_points[0] = sf::Vertex(body_pos + sf::Vector2f(0.0f, 0.0f), sf::Color(21, 25, 30)); - gradient_points[1] = sf::Vertex(body_pos + sf::Vector2f(body_size.x, 0.0f), sf::Color(21, 25, 30)); - gradient_points[2] = sf::Vertex(body_pos + sf::Vector2f(body_size.x, height), sf::Color(0, 0, 0, 0)); - gradient_points[3] = sf::Vertex(body_pos + sf::Vector2f(0.0f, height), sf::Color(0, 0, 0, 0)); + gradient_points[0] = sf::Vertex(body_pos + sf::Vector2f(0.0f, 0.0f), color); + gradient_points[1] = sf::Vertex(body_pos + sf::Vector2f(body_size.x, 0.0f), color); + gradient_points[2] = sf::Vertex(body_pos + sf::Vector2f(body_size.x, height), sf::Color(color.r, color.g, color.b, 0)); + gradient_points[3] = sf::Vertex(body_pos + sf::Vector2f(0.0f, height), sf::Color(color.r, color.g, color.b, 0)); window.draw(gradient_points, 4, sf::Quads); } @@ -490,7 +544,9 @@ namespace QuickMedia { items_dirty = DirtyState::FALSE; } - bool body_size_changed = std::abs(size.x - body_size.x) > 0.1f || std::abs(size.y - body_size.y) > 0.1f; + const bool rendering_card_view = card_view && card_view_enabled; + + const bool body_size_changed = std::abs(size.x - body_size.x) > 0.1f || std::abs(size.y - body_size.y) > 0.1f; if(body_size_changed) body_size = size; @@ -498,11 +554,14 @@ namespace QuickMedia { const float scissor_y = pos.y; pos.y = 0.0f; - pos.x += body_spacing[body_theme].body_padding_horizontal; + if(!rendering_card_view) + pos.x += body_spacing[body_theme].body_padding_horizontal; + if(attach_side == AttachSide::TOP) pos.y += body_spacing[body_theme].body_padding_vertical; - size.x = std::max(0.0f, size.x - body_spacing[body_theme].body_padding_horizontal * 2.0f); + if(!rendering_card_view) + size.x = std::max(0.0f, size.x - body_spacing[body_theme].body_padding_horizontal * 2.0f); float frame_time = frame_timer.restart().asSeconds(); if(frame_time > 0.01666f) @@ -532,6 +591,8 @@ namespace QuickMedia { selected_item_fits_in_body = true; top_cut_off = false; bottom_cut_off = false; + num_columns = 1; + selected_column = 0; const int selected_item_diff = selected_item - prev_selected_item; prev_selected_item = selected_item; @@ -618,19 +679,23 @@ namespace QuickMedia { if(swiping_enabled && grabbed_left_side) pos.x += body_swipe_x; - const int selected_prev_item = get_previous_visible_item(selected_item); - const bool selected_merge_with_previous = selected_prev_item != -1 && body_item_merge_handler && body_item_merge_handler(items[selected_prev_item].get(), items[selected_item].get()); - get_item_height(items[selected_item].get(), size.x, true, true, selected_merge_with_previous, selected_item); - selected_item_fits_in_body = items[selected_item]->last_loaded_height < size.y; - if(selected_item_fits_in_body) - selected_scrolled = 0.0f; + if(!rendering_card_view) { + const int selected_prev_item = get_previous_visible_item(selected_item); + const bool selected_merge_with_previous = selected_prev_item != -1 && body_item_merge_handler && body_item_merge_handler(items[selected_prev_item].get(), items[selected_item].get()); + get_item_height(items[selected_item].get(), size.x, true, true, selected_merge_with_previous, selected_item); + selected_item_fits_in_body = items[selected_item]->last_loaded_height < size.y; + if(selected_item_fits_in_body) + selected_scrolled = 0.0f; + } const sf::Vector2u window_size = window.getSize(); const sf::View prev_view = window.getView(); - sf::View new_view(sf::FloatRect(0.0f, 0.0f, window_size.x, size.y)); - new_view.setViewport(sf::FloatRect(0.0f, scissor_y / (float)window_size.y, 1.0f, size.y / (float)window_size.y)); - window.setView(new_view); + if(!rendering_card_view) { + sf::View new_view(sf::FloatRect(0.0f, 0.0f, window_size.x, size.y)); + new_view.setViewport(sf::FloatRect(0.0f, scissor_y / (float)window_size.y, 1.0f, size.y / (float)window_size.y)); + window.setView(new_view); + } bool instant_move = body_size_changed; if(target_y_set == TargetSetState::SET) { @@ -640,155 +705,25 @@ namespace QuickMedia { const float speed = 30.0f; - const float item_background_height_diff = item_background_target_height - item_background_prev_height; - const float item_background_height_speed = instant_move ? 1000.0f : speed; - const float item_background_new_height = item_background_prev_height + (item_background_height_diff * std::min(1.0f, frame_time * item_background_height_speed)); - item_background.set_size(sf::Vector2f(size.x, item_background_new_height)); - item_background_prev_height = item_background_new_height; + const sf::Vector2f item_background_size_diff = item_background_target_size - item_background_prev_size; + const float item_background_size_speed = instant_move ? 1000.0f : speed; + const sf::Vector2f item_background_new_size = item_background_prev_size + (item_background_size_diff * std::min(1.0f, frame_time * item_background_size_speed)); + item_background_prev_size = item_background_new_size; - const float item_background_pos_diff = item_background_target_pos_y - item_background_prev_pos_y; + const sf::Vector2f item_background_pos_diff = item_background_target_pos - item_background_prev_pos; const float item_background_move_speed = instant_move ? 1000.0f : speed; - float item_background_new_pos_y = item_background_prev_pos_y + (item_background_pos_diff * std::min(1.0f, frame_time * item_background_move_speed)); + sf::Vector2f item_background_new_pos = item_background_prev_pos + (item_background_pos_diff * std::min(1.0f, frame_time * item_background_move_speed)); if(selected_item_fits_in_body) { - item_background_new_pos_y = std::min(item_background_new_pos_y, size.y - item_background_new_height - body_spacing[body_theme].spacing_y); - item_background_new_pos_y = std::max(item_background_new_pos_y, 0.0f); + item_background_new_pos.y = std::min(item_background_new_pos.y, size.y - item_background_new_size.y - body_spacing[body_theme].spacing_y); + item_background_new_pos.y = std::max(item_background_new_pos.y, 0.0f); } - item_background.set_position(sf::Vector2f(pos.x, item_background_new_pos_y)); - item_background_prev_pos_y = item_background_new_pos_y; + item_background_prev_pos = item_background_new_pos; - if(prev_num_visible_items > 0 && render_selected_item_bg && body_theme == BODY_THEME_MINIMAL) { - item_background.set_color(sf::Color(55, 60, 68)); - item_background.set_band(0.0f, 0.0f); - item_background.draw(window); - } - - int index; - if(attach_side == AttachSide::TOP) { - if(page_scroll > 0.0) - page_scroll = 0.0; - pos.y += page_scroll; - index = get_next_visible_item(-1); - - if(pos.y + selected_scrolled > 0.0f) - selected_scrolled = 0.0f; + float body_total_height = 0.0f; + if(rendering_card_view) { + draw_card_view(window, pos, size, window_size, scissor_y); } else { - if(page_scroll < 0.0) - page_scroll = 0.0; - pos.y += size.y; - pos.y += page_scroll; - index = get_previous_visible_item(num_items); - - if(pos.y + selected_scrolled < size.y) - selected_scrolled = 0.0f; - } - - pos.y += selected_scrolled; - const float pos_y_start = pos.y; - - BodyItem *prev_body_item = nullptr; - const double height_move_speed = 1000.0f; - - // TODO: Improve performance. Skip items that are obviously not visible or anywhere near the body. Limit loop to 100-200 items around the selected item - while(index != -1) { - BodyItem *item = items[index].get(); - assert(item->visible); - - int prev_index; - if(attach_side == AttachSide::BOTTOM) { - prev_index = get_previous_visible_item(index); - if(prev_index == -1) - prev_body_item = nullptr; - else - prev_body_item = items[prev_index].get(); - } - - const bool merge_with_previous = body_item_merge_handler && body_item_merge_handler(prev_body_item, item); - if(attach_side == AttachSide::TOP && merge_with_previous) - pos.y -= body_spacing[body_theme].spacing_y; - - get_item_height(item, size.x, false, true, merge_with_previous, index); - const float item_height_diff = item->last_loaded_height - item->current_loaded_height; - const float add_height = item_height_diff * std::min(1.0, frame_time * height_move_speed); - item->current_loaded_height += add_height; - //page_scroll += add_height; - - float top_y; - if(attach_side == AttachSide::TOP) - top_y = pos.y; - else - top_y = pos.y - (item->current_loaded_height + body_spacing[body_theme].spacing_y); - - if(top_y < 0.0f) { - top_cut_off = true; - if(index == selected_item) - selected_line_top_visible = false; - } - - if(top_y + item->current_loaded_height > size.y) { - bottom_cut_off = true; - if(index == selected_item) - selected_line_bottom_visible = false; - } - - const bool is_item_visible_in_body = top_y + item->current_loaded_height >= 0.0f && top_y <= size.y; - if(is_item_visible_in_body || index == selected_item) { - get_item_height(item, size.x, true, true, merge_with_previous, index); - const float item_height_diff = item->last_loaded_height - item->current_loaded_height; - const float add_height = item_height_diff * std::min(1.0, frame_time * height_move_speed); - item->current_loaded_height += add_height; - if(attach_side == AttachSide::BOTTOM) - pos.y -= (item->current_loaded_height + body_spacing[body_theme].spacing_y); - //page_scroll += add_height; - - //const float top_y_clamped = clamp(pos.y, 0.0f, size.y); - //const float bottom_y_clamped = std::min(pos.y + item->current_loaded_height, size.y); - - //float offset_y = 0.0f; - //if(pos.y < 0.0f) - // offset_y = pos.y; - //else if(pos.y > size.y) - // offset_y = size.y - pos.y; - - //sf::View new_view(sf::FloatRect(0.0f, 0.0f, window_size.x, scissor_y + bottom_y_clamped)); - //new_view.setViewport(sf::FloatRect(0.0f, (scissor_y + top_y_clamped) / (float)window_size.y, 1.0f, (scissor_y + bottom_y_clamped) / (float)window_size.y)); - //window.setView(new_view); - - draw_item(window, item, pos/*sf::Vector2f(pos.x, offset_y)*/, size, item->current_loaded_height, index, content_progress, true, merge_with_previous); - handle_item_render(item, pos, size, item->current_loaded_height, index); - ++num_visible_items; - - if(first_visible_item == -1 || index < first_visible_item) - first_visible_item = index; - if(last_visible_item == -1 || index > last_visible_item) - last_visible_item = index; - - if(attach_side == AttachSide::TOP) - pos.y += (item->current_loaded_height + body_spacing[body_theme].spacing_y); - } else { - if(attach_side == AttachSide::TOP) - pos.y += (item->current_loaded_height + body_spacing[body_theme].spacing_y); - else - pos.y -= (item->current_loaded_height + body_spacing[body_theme].spacing_y); - - if(item->keep_alive_frames == 0) { - clear_body_item_cache(item); - // TODO: Make sure the embedded item is not referencing another item in the |items| list - if(item->embedded_item) - clear_body_item_cache(item->embedded_item.get()); - } else { - --item->keep_alive_frames; - } - } - - if(attach_side == AttachSide::BOTTOM && merge_with_previous) - pos.y += body_spacing[body_theme].spacing_y; - - if(attach_side == AttachSide::TOP) { - prev_body_item = item; - index = get_next_visible_item(index); - } else { - index = prev_index; - } + body_total_height = draw_list_view(window, pos, size, prev_num_visible_items, content_progress); } window.setView(prev_view); @@ -797,14 +732,15 @@ namespace QuickMedia { // TODO: Move up where scroll is limited? then we wont delay this by 1 frame creating a small scroll overflow only in the opposite direction of attach side. // Also take |selected_scrolled| into consideration // Limit scroll in the opposide direction of attach side, since the scroll is already limited for the attach side above (with a simple check) - if(attach_side == AttachSide::TOP) { - const float body_total_height = pos.y - pos_y_start; - if(top_cut_off && !bottom_cut_off && body_total_height > (size.y - body_spacing[body_theme].body_padding_vertical)) - page_scroll = -(body_total_height - (size.y - body_spacing[body_theme].body_padding_vertical)); - } else { - const float body_total_height = pos_y_start - pos.y; - if(bottom_cut_off && !top_cut_off && body_total_height > size.y) - page_scroll = (body_total_height - size.y); + if(!rendering_card_view) { + if(attach_side == AttachSide::TOP) { + if(top_cut_off && !bottom_cut_off && body_total_height > (size.y - body_spacing[body_theme].body_padding_vertical)) + page_scroll = -(body_total_height - (size.y - body_spacing[body_theme].body_padding_vertical)); + } else { + body_total_height = -body_total_height; + if(bottom_cut_off && !top_cut_off && body_total_height > size.y) + page_scroll = (body_total_height - size.y); + } } mouse_left_clicked = false; @@ -818,8 +754,8 @@ namespace QuickMedia { if(is_touch_enabled()) return; - const float item_target_top_diff = item_background_target_pos_y - selected_scrolled - body_spacing[body_theme].body_padding_vertical; - const float item_target_bottom_diff = (item_background_target_pos_y - selected_scrolled + item_background_target_height + body_spacing[body_theme].spacing_y) - size.y; + const float item_target_top_diff = item_background_target_pos.y - selected_scrolled - body_spacing[body_theme].body_padding_vertical; + const float item_target_bottom_diff = (item_background_target_pos.y - selected_scrolled + item_background_target_size.y + body_spacing[body_theme].spacing_y) - size.y; if(item_target_top_diff < 0.0f || !selected_item_fits_in_body) { //extra_scroll_target -= item_target_top_diff; stuck_direction = StuckDirection::TOP; @@ -855,10 +791,12 @@ namespace QuickMedia { body_item->dirty = false; // TODO: Find a way to optimize fromUtf8 sf::String str = sf::String::fromUtf8(body_item->get_title().begin(), body_item->get_title().end()); - if(body_item->title_text) + if(body_item->title_text) { body_item->title_text->setString(std::move(str)); - else + body_item->title_text->setMaxWidth(width); + } else { body_item->title_text = std::make_unique<Text>(std::move(str), false, std::floor(16 * get_ui_scale()), width, title_mark_urls); + } body_item->title_text->setFillColor(body_item->get_title_color()); body_item->title_text->updateGeometry(); } @@ -866,10 +804,12 @@ namespace QuickMedia { if(body_item->dirty_description) { body_item->dirty_description = false; sf::String str = sf::String::fromUtf8(body_item->get_description().begin(), body_item->get_description().end()); - if(body_item->description_text) + if(body_item->description_text) { body_item->description_text->setString(std::move(str)); - else + body_item->description_text->setMaxWidth(width); + } else { body_item->description_text = std::make_unique<Text>(std::move(str), false, std::floor(14 * get_ui_scale()), width, true); + } body_item->description_text->setFillColor(body_item->get_description_color()); body_item->description_text->updateGeometry(); } @@ -877,10 +817,12 @@ namespace QuickMedia { if(body_item->dirty_author) { body_item->dirty_author = false; sf::String str = sf::String::fromUtf8(body_item->get_author().begin(), body_item->get_author().end()); - if(body_item->author_text) + if(body_item->author_text) { body_item->author_text->setString(std::move(str)); - else + body_item->author_text->setMaxWidth(width); + } else { body_item->author_text = std::make_unique<Text>(std::move(str), true, std::floor(14 * get_ui_scale()), width); + } body_item->author_text->setFillColor(body_item->get_author_color()); body_item->author_text->updateGeometry(); } @@ -907,10 +849,11 @@ namespace QuickMedia { */ strftime(time_str, sizeof(time_str) - 1, "%a %b %d %H:%M", &message_tm); - if(body_item->timestamp_text) + if(body_item->timestamp_text) { body_item->timestamp_text->setString(time_str); - else + } else { body_item->timestamp_text = std::make_unique<sf::Text>(time_str, *FontLoader::get_font(FontLoader::FontType::LATIN), std::floor(10 * get_ui_scale())); + } body_item->timestamp_text->setFillColor(sf::Color(185, 190, 198, 100)); } @@ -940,7 +883,7 @@ namespace QuickMedia { sf::Vector2i Body::get_item_thumbnail_size(BodyItem *item) const { sf::Vector2i content_size; - sf::Vector2i thumbnail_max_size_scaled(std::floor(thumbnail_max_size.x * get_ui_scale()), std::floor(thumbnail_max_size.y * get_ui_scale())); + sf::Vector2i thumbnail_max_size_scaled = card_view_enabled ? card_max_image_size : sf::Vector2i(thumbnail_max_size.x * get_ui_scale(), thumbnail_max_size.y * get_ui_scale()); if(item->thumbnail_size.x > 0 && item->thumbnail_size.y > 0) content_size = clamp_to_size(sf::Vector2i(std::floor(item->thumbnail_size.x * get_ui_scale()), std::floor(item->thumbnail_size.y * get_ui_scale())), thumbnail_max_size_scaled); else @@ -966,11 +909,8 @@ namespace QuickMedia { return -1; } - static sf::Vector2f to_vec2f(const sf::Vector2i &vec) { - return sf::Vector2f(vec.x, vec.y); - } - void Body::draw_item(sf::RenderWindow &window, BodyItem *item, sf::Vector2f pos, sf::Vector2f size, bool include_embedded_item, bool is_embedded) { + // TODO: What about when |card_view| is used? item->keep_alive_frames = 3; get_item_height(item, size.x, true, false, false, -1); draw_item(window, item, pos, size, size.y + body_spacing[body_theme].spacing_y, -1, Json::Value::nullSingleton(), include_embedded_item); @@ -992,9 +932,9 @@ namespace QuickMedia { return ""; } - void Body::handle_item_render(BodyItem *item, const sf::Vector2f pos, const sf::Vector2f size, const float item_height, int item_index) { + void Body::handle_item_render(const sf::Vector2f pos, const float item_width, const float item_height, int item_index) { if(body_item_select_callback && mouse_left_clicked && !clicked_body_item && click_counts && std::abs(mouse_scroll_accel.y) < 5.0f) { - sf::FloatRect item_box(pos + body_pos, sf::Vector2f(size.x, item_height)); + sf::FloatRect item_box(pos + body_pos, sf::Vector2f(item_width, item_height)); if(item_box.contains(mouse_click_pos) && item_box.contains(mouse_release_pos) && mouse_press_pixels_moved_abs <= 25.0) { clicked_body_item = items[item_index]; set_selected_item(item_index, false); @@ -1002,23 +942,341 @@ namespace QuickMedia { } if(item_index == selected_item) { - item_background_target_pos_y = pos.y; - item_background_target_height = item_height; + item_background_target_pos = pos; + item_background_target_size = sf::Vector2f(item_width, item_height); if(target_y_set == TargetSetState::NOT_SET) target_y_set = TargetSetState::SET; } } - static sf::Color interpolate_colors(sf::Color source, sf::Color target, double progress) { - int diff_r = (int)target.r - (int)source.r; - int diff_g = (int)target.g - (int)source.g; - int diff_b = (int)target.b - (int)source.b; - int diff_a = (int)target.a - (int)source.a; - return sf::Color( - source.r + diff_r * progress, - source.g + diff_g * progress, - source.b + diff_b * progress, - source.a + diff_a * progress); + float Body::draw_list_view(sf::RenderWindow &window, sf::Vector2f pos, sf::Vector2f size, const int prev_num_visible_items, const Json::Value &content_progress) { + const int num_items = items.size(); + + if(prev_num_visible_items > 0 && render_selected_item_bg && body_theme == BODY_THEME_MINIMAL) { + item_background.set_position(sf::Vector2f(pos.x, item_background_prev_pos.y)); + item_background.set_size(item_background_prev_size); + item_background.set_color(sf::Color(60, 65, 73)); + item_background.set_band(sf::Vector2f(0.0f, 0.0f), sf::Vector2f(0.0f, 0.0f)); + item_background.draw(window); + } + + int index; + if(attach_side == AttachSide::TOP) { + if(page_scroll > 0.0) + page_scroll = 0.0; + pos.y += page_scroll; + index = get_next_visible_item(-1); + + if(pos.y + selected_scrolled > 0.0f) + selected_scrolled = 0.0f; + } else { + if(page_scroll < 0.0) + page_scroll = 0.0; + pos.y += size.y; + pos.y += page_scroll; + index = get_previous_visible_item(num_items); + + if(pos.y + selected_scrolled < size.y) + selected_scrolled = 0.0f; + } + + pos.y += selected_scrolled; + const float pos_y_start = pos.y; + + BodyItem *prev_body_item = nullptr; + + // TODO: Improve performance. Skip items that are obviously not visible or anywhere near the body. Limit loop to 100-200 items around the selected item + while(index != -1) { + BodyItem *item = items[index].get(); + assert(item->visible); + + int prev_index; + if(attach_side == AttachSide::BOTTOM) { + prev_index = get_previous_visible_item(index); + if(prev_index == -1) + prev_body_item = nullptr; + else + prev_body_item = items[prev_index].get(); + } + + const bool merge_with_previous = body_item_merge_handler && body_item_merge_handler(prev_body_item, item); + if(attach_side == AttachSide::TOP && merge_with_previous) + pos.y -= body_spacing[body_theme].spacing_y; + + get_item_height(item, size.x, false, true, merge_with_previous, index); + item->current_loaded_height = item->last_loaded_height; + + float top_y; + if(attach_side == AttachSide::TOP) + top_y = pos.y; + else + top_y = pos.y - (item->current_loaded_height + body_spacing[body_theme].spacing_y); + + if(top_y < 0.0f) { + top_cut_off = true; + if(index == selected_item) + selected_line_top_visible = false; + } + + if(top_y + item->current_loaded_height > size.y) { + bottom_cut_off = true; + if(index == selected_item) + selected_line_bottom_visible = false; + } + + const bool is_item_visible_in_body = top_y + item->current_loaded_height >= 0.0f && top_y <= size.y; + if(is_item_visible_in_body || index == selected_item) { + get_item_height(item, size.x, true, true, merge_with_previous, index); + item->current_loaded_height = item->last_loaded_height; + if(attach_side == AttachSide::BOTTOM) + pos.y -= (item->current_loaded_height + body_spacing[body_theme].spacing_y); + //page_scroll += add_height; + + //const float top_y_clamped = clamp(pos.y, 0.0f, size.y); + //const float bottom_y_clamped = std::min(pos.y + item->current_loaded_height, size.y); + + //float offset_y = 0.0f; + //if(pos.y < 0.0f) + // offset_y = pos.y; + //else if(pos.y > size.y) + // offset_y = size.y - pos.y; + + //sf::View new_view(sf::FloatRect(0.0f, 0.0f, window_size.x, scissor_y + bottom_y_clamped)); + //new_view.setViewport(sf::FloatRect(0.0f, (scissor_y + top_y_clamped) / (float)window_size.y, 1.0f, (scissor_y + bottom_y_clamped) / (float)window_size.y)); + //window.setView(new_view); + + draw_item(window, item, pos/*sf::Vector2f(pos.x, offset_y)*/, size, item->current_loaded_height, index, content_progress, true, merge_with_previous); + handle_item_render(pos, size.x, item->current_loaded_height, index); + ++num_visible_items; + + if(first_visible_item == -1 || index < first_visible_item) + first_visible_item = index; + if(last_visible_item == -1 || index > last_visible_item) + last_visible_item = index; + + if(attach_side == AttachSide::TOP) + pos.y += (item->current_loaded_height + body_spacing[body_theme].spacing_y); + } else { + if(attach_side == AttachSide::TOP) + pos.y += (item->current_loaded_height + body_spacing[body_theme].spacing_y); + else + pos.y -= (item->current_loaded_height + body_spacing[body_theme].spacing_y); + + if(item->keep_alive_frames == 0) { + clear_body_item_cache(item); + // TODO: Make sure the embedded item is not referencing another item in the |items| list + if(item->embedded_item) + clear_body_item_cache(item->embedded_item.get()); + } else { + --item->keep_alive_frames; + } + } + + if(attach_side == AttachSide::BOTTOM && merge_with_previous) + pos.y += body_spacing[body_theme].spacing_y; + + if(attach_side == AttachSide::TOP) { + prev_body_item = item; + index = get_next_visible_item(index); + } else { + index = prev_index; + } + } + + return pos.y - pos_y_start; + } + + void Body::draw_card_view(sf::RenderWindow &window, sf::Vector2f pos, sf::Vector2f size, sf::Vector2u window_size, float scissor_y) { + num_columns = size.x / card_width; + + int space_left_column = size.x - (num_columns * card_width); + int space_left_column_each = 0; + // TODO: Spacing for 1 column + if(num_columns > 1) { + space_left_column_each = space_left_column / (2 + num_columns - 1); + if(space_left_column_each < min_column_spacing) { + num_columns = (size.x + min_column_spacing) / (card_width + min_column_spacing); + //space_left_column_each = min_column_spacing; + space_left_column = size.x - (num_columns * card_width); + space_left_column_each = num_columns <= 1 ? 0 : space_left_column / (2 + num_columns - 1); + } + } + + if(num_columns <= num_columns_switch_to_list) { + num_columns = 1; + card_view_enabled = false; + draw(window, body_pos, body_size); + card_view_enabled = true; + return; + } + + const int space_left_row_each = space_left_column_each; + + //item_background.set_band_color(sf::Color(60, 65, 73)); + //item_background.set_band(item_background_prev_pos_y - pos.y, item_background_prev_height); + + if(page_scroll > 0.0) + page_scroll = 0.0; + + int item_index = 0; + int drawn_column_index = 0; + int num_visible_rows = 1; + const int num_items = items.size(); + sf::Vector2f pos_offset(space_left_column_each, page_scroll); + + while(item_index < num_items) { + BodyItem *item = items[item_index].get(); + + if(pos_offset.y + card_height <= -body_spacing[body_theme].body_padding_vertical) { + top_cut_off = true; + if(item_index == selected_item) + selected_line_top_visible = false; + } + + if(pos_offset.y >= size.y) { + bottom_cut_off = true; + if(item_index == selected_item) + selected_line_bottom_visible = false; + } + + if(item_index == selected_item) + selected_column = drawn_column_index; + + handle_item_render(pos + pos_offset, card_width, card_height, item_index); + + if(item->visible && pos_offset.y + card_height > -body_spacing[body_theme].body_padding_vertical && pos_offset.y < size.y) { + sf::View new_view(sf::FloatRect(0.0f, 0.0f, window_size.x, size.y)); + new_view.setViewport(sf::FloatRect(0.0f, scissor_y / (float)window_size.y, 1.0f, size.y / (float)window_size.y)); + window.setView(new_view); + + item_background.set_position(pos + pos_offset); + item_background.set_size(sf::Vector2f(card_width, card_height)); + item_background.set_color(sf::Color(33, 37, 44)); + item_background.set_band(item_background_prev_pos - (pos + pos_offset), item_background_prev_size); + item_background.set_band_color(sf::Color(60, 65, 73)); + item_background.draw(window); + + { + get_item_height(item, card_max_image_size.x); + sf::Vector2i thumbnail_size = get_item_thumbnail_size(item); + std::shared_ptr<ThumbnailData> item_thumbnail; + if(draw_thumbnails && !item->thumbnail_url.empty()) + item_thumbnail = AsyncImageLoader::get_instance().get_thumbnail(item->thumbnail_url, item->thumbnail_is_local, thumbnail_size); + + float image_height = 0.0f; + if(item_thumbnail && item_thumbnail->loading_state == LoadingState::APPLIED_TO_TEXTURE && item_thumbnail->texture.getNativeHandle() != 0) { + image.setTexture(item_thumbnail->texture, true); + auto image_size = image.getTexture()->getSize(); + sf::Vector2f image_size_f(image_size.x, image_size.y); + sf::Vector2f content_size = to_vec2f(thumbnail_size); + auto new_image_size = clamp_to_size(image_size_f, content_size); + auto image_scale = get_ratio(image_size_f, new_image_size); + image.setScale(image_scale); + image.setPosition(pos + pos_offset + sf::Vector2f(card_padding_x, card_padding_y) + sf::Vector2f(card_max_image_size.x * 0.5f, 0.0f) - sf::Vector2f(new_image_size.x * 0.5f, 0.0f)); + image_height = new_image_size.y; + if(thumbnail_mask_shader && item->thumbnail_mask_type == ThumbnailMaskType::CIRCLE) { + thumbnail_mask_shader->setUniform("resolution", new_image_size); + window.draw(image, thumbnail_mask_shader); + } else if(rounded_rectangle_mask_shader) { + rounded_rectangle_mask_shader->setUniform("radius", 10.0f); + rounded_rectangle_mask_shader->setUniform("resolution", new_image_size); + window.draw(image, rounded_rectangle_mask_shader); + } else { + window.draw(image); + } + } else if(!item->thumbnail_url.empty()) { + sf::Vector2f content_size = to_vec2f(thumbnail_size); + sf::Vector2f loading_icon_size(loading_icon.getTexture()->getSize().x, loading_icon.getTexture()->getSize().y); + auto new_loading_icon_size = clamp_to_size(loading_icon_size, content_size); + loading_icon.setPosition(pos + pos_offset + sf::Vector2f(card_padding_x, card_padding_y) + to_vec2f(card_max_image_size) * 0.5f); + loading_icon.setScale(get_ratio(loading_icon_size, new_loading_icon_size)); + loading_icon.setRotation(elapsed_time_sec * 400.0); + window.draw(loading_icon); + image_height = content_size.y; + } + + const float text_padding = item_thumbnail ? card_image_text_padding : 0.0f; + + sf::Vector2f text_pos = sf::Vector2f(pos.x, scissor_y + body_spacing[body_theme].body_padding_vertical) + pos_offset + sf::Vector2f(card_padding_x, card_padding_y) + sf::Vector2f(0.0f, image_height + text_padding); + const float text_height = (card_height - card_padding_y * 2.0f) - image_height - text_padding; + + const float underflow_text = text_pos.y - scissor_y; + const float underflow_height = underflow_text < 0.0f ? text_height + underflow_text : text_height; + sf::View new_view(sf::FloatRect(0.0f, 0.0f, window_size.x, underflow_height)); + new_view.setViewport(sf::FloatRect(0.0f, std::max(text_pos.y, scissor_y) / (float)window_size.y, 1.0f, underflow_height / (float)window_size.y)); + window.setView(new_view); + + text_pos.y = std::min(0.0f, underflow_text); + float text_offset_y = 0.0f; + if(item->author_text) { + item->author_text->setPosition(text_pos); + item->author_text->setMaxWidth(card_max_image_size.x); + item->author_text->draw(window); + text_offset_y += item->author_text->getHeight(); + } + + if(item->title_text) { + item->title_text->setPosition(text_pos + sf::Vector2f(0.0f, text_offset_y)); + item->title_text->setMaxWidth(card_max_image_size.x); + item->title_text->draw(window); + text_offset_y += item->title_text->getHeight(); + } + + if(item->description_text) { + item->description_text->setPosition(text_pos + sf::Vector2f(0.0f, text_offset_y)); + item->description_text->setMaxWidth(card_max_image_size.x); + item->description_text->draw(window); + text_offset_y += item->description_text->getHeight(); + } + + const float gradient_height = 5.0f; + if(text_offset_y >= text_height - gradient_height) { + const sf::Vector2f card_bottom(text_pos.x, text_height); + const sf::Color color = item_index == selected_item ? sf::Color(60, 65, 73) : sf::Color(33, 37, 44); + + sf::Vertex gradient_points[4]; + gradient_points[0] = sf::Vertex(card_bottom + sf::Vector2f(0.0f, -gradient_height), sf::Color(color.r, color.g, color.b, 0)); + gradient_points[1] = sf::Vertex(card_bottom + sf::Vector2f(card_max_image_size.x, -gradient_height), sf::Color(color.r, color.g, color.b, 0)); + gradient_points[2] = sf::Vertex(card_bottom + sf::Vector2f(card_max_image_size.x, 0.0f), color); + gradient_points[3] = sf::Vertex(card_bottom + sf::Vector2f(0.0f, 0.0f), color); + window.draw(gradient_points, 4, sf::Quads); + } + } + + ++num_visible_items; + if(first_visible_item == -1 || item_index < first_visible_item) + first_visible_item = item_index; + if(last_visible_item == -1 || item_index > last_visible_item) + last_visible_item = item_index; + } else { + if(item->keep_alive_frames == 0) { + clear_body_item_cache(item); + // TODO: Make sure the embedded item is not referencing another item in the |items| list + if(item->embedded_item) + clear_body_item_cache(item->embedded_item.get()); + } else { + --item->keep_alive_frames; + } + } + + if(item->visible) { + pos_offset.x += card_width + space_left_column_each; + ++drawn_column_index; + + if(drawn_column_index == num_columns) { + drawn_column_index = 0; + ++num_visible_rows; + pos_offset.x = space_left_column_each; + pos_offset.y += card_height + space_left_row_each; + } + } + + ++item_index; + } + + /*const float body_total_height = num_visible_rows * (card_height + space_left_row_each); + if(top_cut_off && !bottom_cut_off && body_total_height > size.y) + page_scroll = -(body_total_height - (size.y - body_spacing[body_theme].body_padding_vertical));*/ } void Body::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, bool include_embedded_item, bool merge_with_previous) { @@ -1040,8 +1298,8 @@ namespace QuickMedia { item_background.set_size(sf::Vector2f(size.x, item_height)); item_background.set_position(item_pos); item_background.set_color(sf::Color(33, 37, 44)); - item_background.set_band(item_background_prev_pos_y - pos.y, item_background_prev_height); - item_background.set_band_color(sf::Color(55, 60, 68)); + item_background.set_band(item_background_prev_pos - pos, item_background_prev_size); + item_background.set_band_color(sf::Color(60, 65, 73)); item_background.draw(window); } @@ -1103,12 +1361,13 @@ namespace QuickMedia { text_offset_x += body_spacing[body_theme].image_padding_x + thumbnail_size.x; } + const float text_max_width = size.x - text_offset_x - body_spacing[body_theme].image_padding_x; const float text_offset_y = std::floor(6.0f * get_ui_scale()); const float timestamp_text_y = std::floor(item_pos.y + padding_y - text_offset_y); if(item->author_text && !merge_with_previous) { item->author_text->setPosition(std::floor(item_pos.x + text_offset_x), std::floor(item_pos.y + padding_y - text_offset_y)); - item->author_text->setMaxWidth(size.x - text_offset_x - body_spacing[body_theme].image_padding_x); + item->author_text->setMaxWidth(text_max_width); item->author_text->draw(window); sf::Vector2f replies_text_pos = item->author_text->getPosition() + sf::Vector2f(0.0f, 5.0f); @@ -1153,7 +1412,7 @@ namespace QuickMedia { //window.draw(title_text); if(item->title_text) { item->title_text->setPosition(std::floor(item_pos.x + text_offset_x), std::floor(item_pos.y + padding_y - text_offset_y)); - item->title_text->setMaxWidth(size.x - text_offset_x - body_spacing[body_theme].image_padding_x); + item->title_text->setMaxWidth(text_max_width); item->title_text->draw(window); item_pos.y += item->title_text->getHeight() - 2.0f + std::floor(3.0f * get_ui_scale()); } @@ -1161,7 +1420,7 @@ namespace QuickMedia { if(item->description_text) { float height_offset = 0.0f; item->description_text->setPosition(std::floor(item_pos.x + text_offset_x), std::floor(item_pos.y + padding_y - text_offset_y) + height_offset); - item->description_text->setMaxWidth(size.x - text_offset_x - body_spacing[body_theme].image_padding_x); + item->description_text->setMaxWidth(text_max_width); item->description_text->draw(window); item_pos.y += item->description_text->getHeight() - 2.0f; } @@ -1173,7 +1432,7 @@ namespace QuickMedia { // TODO: Fix first row wrap-around for(int i = 0; i < item->reactions.size(); ++i) { auto &reaction = item->reactions[i]; - reaction.text->setMaxWidth(size.x - text_offset_x - body_spacing[body_theme].image_padding_x); + reaction.text->setMaxWidth(text_max_width); reaction.text->updateGeometry(); reaction_max_height = std::max(reaction_max_height, reaction.text->getHeight()); reaction.text->setPosition(std::floor(item_pos.x + text_offset_x + reaction_offset_x + body_spacing[body_theme].reaction_background_padding_x), std::floor(item_pos.y + padding_y - 4.0f + body_spacing[body_theme].reaction_background_padding_y)); @@ -1251,8 +1510,10 @@ namespace QuickMedia { // image_height = content_size.y; } + const float text_max_width = (card_view && card_view_enabled) ? width : (width - text_offset_x - body_spacing[body_theme].image_padding_x); + if(load_texture) - update_dirty_state(item, width - text_offset_x - body_spacing[body_theme].image_padding_x); + update_dirty_state(item, text_max_width); float item_height = 0.0f; if(item->title_text) { @@ -1277,7 +1538,7 @@ namespace QuickMedia { float reaction_max_height = 0.0f; for(int i = 0; i < item->reactions.size(); ++i) { auto &reaction = item->reactions[i]; - reaction.text->setMaxWidth(width - text_offset_x - body_spacing[body_theme].image_padding_x); + reaction.text->setMaxWidth(text_max_width); reaction.text->updateGeometry(); reaction_max_height = std::max(reaction_max_height, reaction.text->getHeight()); reaction_offset_x += reaction.text->getWidth() + body_spacing[body_theme].reaction_background_padding_x * 2.0f + body_spacing[body_theme].reaction_spacing_x; diff --git a/src/Entry.cpp b/src/Entry.cpp index 7e1b6d9..28e3034 100644 --- a/src/Entry.cpp +++ b/src/Entry.cpp @@ -16,7 +16,7 @@ namespace QuickMedia { draw_background(true), text("", false, std::floor(16 * get_ui_scale()), 0.0f), width(0.0f), - background(sf::Vector2f(1.0f, 1.0f), 10.0f, sf::Color(55, 60, 68), rounded_rectangle_shader), + background(sf::Vector2f(1.0f, 1.0f), 10.0f, sf::Color(60, 65, 73), rounded_rectangle_shader), placeholder(placeholder_text, *FontLoader::get_font(FontLoader::FontType::LATIN), std::floor(16 * get_ui_scale())), mouse_left_inside(false) { diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 9670f68..2926637 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -1056,7 +1056,7 @@ namespace QuickMedia { } if(strcmp(plugin_name, "launcher") == 0) { - auto pipe_body = create_body(true); + auto pipe_body = create_body(); pipe_body->items.push_back(create_launcher_body_item("4chan", "4chan", resources_root + "icons/4chan_launcher.png")); pipe_body->items.push_back(create_launcher_body_item("Manga (all)", "manga", "")); pipe_body->items.push_back(create_launcher_body_item("Mangadex", "mangadex", resources_root + "icons/mangadex_launcher.png")); @@ -1074,33 +1074,33 @@ namespace QuickMedia { pipe_body->items.push_back(create_launcher_body_item("YouTube (audio only)", "youtube-audio", resources_root + "icons/yt_launcher.png")); tabs.push_back(Tab{std::move(pipe_body), std::make_unique<PipePage>(this, "Select plugin to launch"), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); } else if(strcmp(plugin_name, "manganelo") == 0) { - tabs.push_back(Tab{create_body(), std::make_unique<ManganeloSearchPage>(this), create_search_bar("Search...", 400)}); + tabs.push_back(Tab{create_body(false, true), std::make_unique<ManganeloSearchPage>(this), create_search_bar("Search...", 400)}); auto history_page = std::make_unique<HistoryPage>(this, tabs.front().page.get(), HistoryType::MANGA); tabs.push_back(Tab{create_body(), std::move(history_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); } else if(strcmp(plugin_name, "manganelos") == 0) { auto search_page = std::make_unique<MangaGenericSearchPage>(this, plugin_name, "http://manganelos.com/"); add_manganelos_handlers(search_page.get()); - tabs.push_back(Tab{create_body(), std::move(search_page), create_search_bar("Search...", 400)}); + tabs.push_back(Tab{create_body(false, true), std::move(search_page), create_search_bar("Search...", 400)}); auto history_page = std::make_unique<HistoryPage>(this, tabs.front().page.get(), HistoryType::MANGA); tabs.push_back(Tab{create_body(), std::move(history_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); } else if(strcmp(plugin_name, "mangatown") == 0) { auto search_page = std::make_unique<MangaGenericSearchPage>(this, plugin_name, "https://www.mangatown.com/"); add_mangatown_handlers(search_page.get()); - tabs.push_back(Tab{create_body(), std::move(search_page), create_search_bar("Search...", 400)}); + tabs.push_back(Tab{create_body(false, true), std::move(search_page), create_search_bar("Search...", 400)}); auto history_page = std::make_unique<HistoryPage>(this, tabs.front().page.get(), HistoryType::MANGA); tabs.push_back(Tab{create_body(), std::move(history_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); } else if(strcmp(plugin_name, "mangakatana") == 0) { auto search_page = std::make_unique<MangaGenericSearchPage>(this, plugin_name, "https://mangakatana.com/", false); add_mangakatana_handlers(search_page.get()); - tabs.push_back(Tab{create_body(), std::move(search_page), create_search_bar("Search...", 400)}); + tabs.push_back(Tab{create_body(false, true), std::move(search_page), create_search_bar("Search...", 400)}); auto history_page = std::make_unique<HistoryPage>(this, tabs.front().page.get(), HistoryType::MANGA); tabs.push_back(Tab{create_body(), std::move(history_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); } else if(strcmp(plugin_name, "mangadex") == 0) { - tabs.push_back(Tab{create_body(), std::make_unique<MangadexSearchPage>(this), create_search_bar("Search...", 400)}); + tabs.push_back(Tab{create_body(false, true), std::make_unique<MangadexSearchPage>(this), create_search_bar("Search...", 400)}); upgrade_legacy_mangadex_ids(this, tabs.back().page.get()); auto history_page = std::make_unique<HistoryPage>(this, tabs.front().page.get(), HistoryType::MANGA); @@ -1108,7 +1108,7 @@ namespace QuickMedia { } else if(strcmp(plugin_name, "readm") == 0) { auto search_page = std::make_unique<MangaGenericSearchPage>(this, plugin_name, "https://readm.org/"); add_readm_handlers(search_page.get()); - tabs.push_back(Tab{create_body(), std::move(search_page), create_search_bar("Search...", 400)}); + tabs.push_back(Tab{create_body(false, true), std::move(search_page), create_search_bar("Search...", 400)}); auto history_page = std::make_unique<HistoryPage>(this, tabs.front().page.get(), HistoryType::MANGA); tabs.push_back(Tab{create_body(), std::move(history_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); @@ -1172,33 +1172,33 @@ namespace QuickMedia { tabs.push_back(Tab{std::move(pipe_body), std::make_unique<PipePage>(this), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); } else if(strcmp(plugin_name, "youtube") == 0) { start_tab_index = 1; - tabs.push_back(Tab{create_body(), std::make_unique<YoutubeSubscriptionsPage>(this), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); - tabs.push_back(Tab{create_body(), std::make_unique<YoutubeSearchPage>(this), create_search_bar("Search...", 350)}); + tabs.push_back(Tab{create_body(false, true), std::make_unique<YoutubeSubscriptionsPage>(this), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + tabs.push_back(Tab{create_body(false, true), std::make_unique<YoutubeSearchPage>(this), create_search_bar("Search...", 350)}); auto recommended_page = std::make_unique<YoutubeRecommendedPage>(this); - tabs.push_back(Tab{create_body(), std::move(recommended_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + tabs.push_back(Tab{create_body(false, true), std::move(recommended_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); - auto history_body = create_body(); + auto history_body = create_body(false, true); auto history_page = std::make_unique<HistoryPage>(this, tabs.front().page.get(), HistoryType::YOUTUBE); tabs.push_back(Tab{std::move(history_body), std::move(history_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); } else if(strcmp(plugin_name, "pornhub") == 0) { auto search_page = std::make_unique<MediaGenericSearchPage>(this, "https://www.pornhub.com/", sf::Vector2i(320/1.5f, 180/1.5f)); add_pornhub_handlers(search_page.get()); - tabs.push_back(Tab{create_body(), std::move(search_page), create_search_bar("Search...", 500)}); + tabs.push_back(Tab{create_body(false, true), std::move(search_page), create_search_bar("Search...", 500)}); } else if(strcmp(plugin_name, "spankbang") == 0) { auto search_page = std::make_unique<MediaGenericSearchPage>(this, "https://spankbang.com/", sf::Vector2i(500/2.5f, 281/2.5f)); add_spankbang_handlers(search_page.get()); - tabs.push_back(Tab{create_body(), std::move(search_page), create_search_bar("Search...", 500)}); + tabs.push_back(Tab{create_body(false, true), std::move(search_page), create_search_bar("Search...", 500)}); } else if(strcmp(plugin_name, "xvideos") == 0) { auto search_page = std::make_unique<MediaGenericSearchPage>(this, "https://www.xvideos.com/", sf::Vector2i(352/1.5f, 198/1.5f)); add_xvideos_handlers(search_page.get()); - tabs.push_back(Tab{create_body(), std::move(search_page), create_search_bar("Search...", 500)}); + tabs.push_back(Tab{create_body(false, true), std::move(search_page), create_search_bar("Search...", 500)}); } else if(strcmp(plugin_name, "xhamster") == 0) { auto search_page = std::make_unique<MediaGenericSearchPage>(this, "https://xhamster.com/", sf::Vector2i(240, 135)); add_xhamster_handlers(search_page.get()); - tabs.push_back(Tab{create_body(), std::move(search_page), create_search_bar("Search...", 500)}); + tabs.push_back(Tab{create_body(false, true), std::move(search_page), create_search_bar("Search...", 500)}); } else if(strcmp(plugin_name, "soundcloud") == 0) { - tabs.push_back(Tab{create_body(), std::make_unique<SoundcloudSearchPage>(this), create_search_bar("Search...", 500)}); + tabs.push_back(Tab{create_body(false, true), std::make_unique<SoundcloudSearchPage>(this), create_search_bar("Search...", 500)}); no_video = true; } else if(strcmp(plugin_name, "matrix") == 0) { assert(!matrix); @@ -1418,8 +1418,9 @@ namespace QuickMedia { body_size = sf::Vector2f(body_width, window_size.y - search_bottom - tab_h); } - std::unique_ptr<Body> Program::create_body(bool plain_text_list) { + std::unique_ptr<Body> Program::create_body(bool plain_text_list, bool prefer_card_view) { auto body = std::make_unique<Body>(plain_text_list ? BODY_THEME_MINIMAL : BODY_THEME_MODERN_SPACIOUS, loading_icon, &rounded_rectangle_shader, &rounded_rectangle_mask_shader); + body->card_view = prefer_card_view; body->thumbnail_mask_shader = &circle_mask_shader; return body; } @@ -1592,7 +1593,7 @@ namespace QuickMedia { Tabs ui_tabs(&rounded_rectangle_shader); for(auto &tab : tabs) { - ui_tabs.add_tab(tab.page->get_title()); + ui_tabs.add_tab(tab.page->get_title(), tab.body.get()); } ui_tabs.set_selected(start_tab_index); @@ -2607,18 +2608,18 @@ namespace QuickMedia { std::vector<Tab> tabs; if(search_page) { - tabs.push_back(Tab{create_body(), std::move(search_page), create_search_bar("Search...", search_delay)}); + tabs.push_back(Tab{create_body(false, true), std::move(search_page), create_search_bar("Search...", search_delay)}); } if(comments_page) { tabs.push_back(Tab{create_body(), std::move(comments_page), nullptr}); } if(related_videos_page) { - auto related_videos_body = create_body(); + auto related_videos_body = create_body(false, true); related_videos_body->items = related_videos; tabs.push_back(Tab{std::move(related_videos_body), std::move(related_videos_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); } if(channels_page) { - tabs.push_back(Tab{create_body(), std::move(channels_page), create_search_bar("Search...", 350)}); + tabs.push_back(Tab{create_body(false, true), std::move(channels_page), create_search_bar("Search...", 350)}); } bool page_changed = false; @@ -3915,14 +3916,9 @@ namespace QuickMedia { bool redraw = true; sf::Event event; - auto body = create_body(); - while (current_page == PageType::CHAT_LOGIN && window.isOpen()) { while (window.pollEvent(event)) { - if(body->on_event(window, event)) - idle_active_handler(); - else - event_idle_handler(event); + event_idle_handler(event); if(event.type == sf::Event::Resized) { window_size.x = event.size.width; @@ -3939,6 +3935,7 @@ namespace QuickMedia { } focused_input = (focused_input + 1) % num_inputs; inputs[focused_input]->caret_visible = true; + idle_active_handler(); } inputs[focused_input]->on_event(event); } @@ -3961,7 +3958,6 @@ namespace QuickMedia { } window.clear(back_color); - body->draw(window, body_pos, body_size); background.draw(window); for(int i = 0; i < num_inputs; ++i) { inputs[i]->update(); @@ -4156,9 +4152,9 @@ namespace QuickMedia { } Tabs ui_tabs(&rounded_rectangle_shader, is_touch_enabled() ? sf::Color::Transparent : back_color); - const int PINNED_TAB_INDEX = ui_tabs.add_tab("Pinned messages (0)"); - const int MESSAGES_TAB_INDEX = ui_tabs.add_tab("Messages"); - const int USERS_TAB_INDEX = ui_tabs.add_tab("Users (0)"); + const int PINNED_TAB_INDEX = ui_tabs.add_tab("Pinned messages (0)", tabs[0].body.get()); + const int MESSAGES_TAB_INDEX = ui_tabs.add_tab("Messages", tabs[1].body.get()); + const int USERS_TAB_INDEX = ui_tabs.add_tab("Users (0)", tabs[2].body.get()); ui_tabs.set_selected(MESSAGES_TAB_INDEX); matrix_chat_page->chat_body = tabs[MESSAGES_TAB_INDEX].body.get(); @@ -6591,7 +6587,7 @@ namespace QuickMedia { auto search_bar = create_search_bar("Search...", SEARCH_DELAY_FILTER); Tabs ui_tabs(&rounded_rectangle_shader); - const int tab_path_index = ui_tabs.add_tab(file_manager_start_dir); + const int tab_path_index = ui_tabs.add_tab(file_manager_start_dir, file_manager_body.get()); search_bar->onTextUpdateCallback = [&file_manager_body](const std::string &text) { file_manager_body->filter_search_fuzzy(text); diff --git a/src/RoundedRectangle.cpp b/src/RoundedRectangle.cpp index 494a254..afd733f 100644 --- a/src/RoundedRectangle.cpp +++ b/src/RoundedRectangle.cpp @@ -5,7 +5,7 @@ namespace QuickMedia { RoundedRectangle::RoundedRectangle(sf::Vector2f size, float radius, sf::Color color, sf::Shader *rounded_rectangle_shader) : - radius(radius), pos(0.0f, 0.0f), size(size), rounded_rectangle_shader(rounded_rectangle_shader), band_y(0.0f), band_height(0.0f), band_color(sf::Color::Transparent) + radius(radius), pos(0.0f, 0.0f), size(size), rounded_rectangle_shader(rounded_rectangle_shader), band_color(sf::Color::Transparent) { assert(rounded_rectangle_shader); vertices[0].color = color; @@ -47,9 +47,9 @@ namespace QuickMedia { return size; } - void RoundedRectangle::set_band(float y, float height) { - band_y = y; - band_height = height; + void RoundedRectangle::set_band(sf::Vector2f pos, sf::Vector2f size) { + band_pos = pos; + band_size = size; } void RoundedRectangle::set_band_color(sf::Color color) { @@ -57,9 +57,10 @@ namespace QuickMedia { } void RoundedRectangle::draw(sf::RenderTarget &target) { + // TODO: Remove these for optimizations. Also do the same in other places where setUniform is called rounded_rectangle_shader->setUniform("radius", radius); - rounded_rectangle_shader->setUniform("band_y", band_y); - rounded_rectangle_shader->setUniform("band_height", band_height); + rounded_rectangle_shader->setUniform("band_pos", band_pos); + rounded_rectangle_shader->setUniform("band_size", band_size); rounded_rectangle_shader->setUniform("band_color", sf::Glsl::Vec4(band_color.r/255.0f, band_color.g/255.0f, band_color.b/255.0f, band_color.a/255.0f)); rounded_rectangle_shader->setUniform("resolution", size); target.draw(vertices, 4, sf::Quads, rounded_rectangle_shader); diff --git a/src/SearchBar.cpp b/src/SearchBar.cpp index f7e732a..91ad2e8 100644 --- a/src/SearchBar.cpp +++ b/src/SearchBar.cpp @@ -11,7 +11,7 @@ // TODO: Use a seperate placeholder sf::Text instead of switching the text to placeholder text.... static const sf::Color text_placeholder_color(255, 255, 255, 100); -static const sf::Color front_color(55, 60, 68); +static const sf::Color front_color(60, 65, 73); static const float background_margin_horizontal = 10.0f + std::floor(5.0f * QuickMedia::get_ui_scale()); static const float padding_top_default = std::floor(10.0f * QuickMedia::get_ui_scale()); static const float padding_bottom_default = std::floor(15.0f * QuickMedia::get_ui_scale()); @@ -106,6 +106,9 @@ namespace QuickMedia { if(!editable) return; + if(sf::Keyboard::isKeyPressed(sf::Keyboard::LControl) || sf::Keyboard::isKeyPressed(sf::Keyboard::RControl)) + return; + if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Backspace) backspace_pressed = true; else if(event.type == sf::Event::KeyReleased && event.key.code == sf::Keyboard::Backspace) diff --git a/src/Tabs.cpp b/src/Tabs.cpp index b671262..e1a0d86 100644 --- a/src/Tabs.cpp +++ b/src/Tabs.cpp @@ -1,4 +1,5 @@ #include "../include/Tabs.hpp" +#include "../include/Body.hpp" #include "../include/ResourceLoader.hpp" #include "../include/Utils.hpp" #include <SFML/Window/Event.hpp> @@ -9,7 +10,7 @@ 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 sf::Color tab_selected_color(60, 65, 73); static const sf::Color arrow_color(255, 255, 255, 175); static const float tab_min_width = 250.0f; static const float tab_margin_x = 10.0f; @@ -28,45 +29,64 @@ namespace QuickMedia { 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)); - tab_labels_utf8.push_back(title); - return tab_texts.size() - 1; + int Tabs::add_tab(const std::string &title, Body *body) { + assert(body); + tabs.push_back({ sf::Text(title, *FontLoader::get_font(FontLoader::FontType::LATIN), tab_text_size), title, body} ); + return tabs.size() - 1; + } + + void Tabs::move_selected_tab(int new_tab) { + const int tab_diff = new_tab - selected_tab; + + if(tab_diff > 0) { + while(selected_tab < new_tab) { + ++selected_tab; + const 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_margin_x) + --tab_offset; + } + } else if(tab_diff < 0) { + while(selected_tab > new_tab) { + --selected_tab; + const float scroll_fixed = scroll + (tab_offset * width_per_tab); + if(scroll_fixed + tab_index_to_x_offset(selected_tab) + tab_background_width < tab_margin_x) + ++tab_offset; + } + } else { + return; + } + + if(on_change_tab) + on_change_tab(selected_tab); } 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 < tab_margin_x) - 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_margin_x) - tab_offset--; - - if(on_change_tab) - on_change_tab(selected_tab); - } + if(event.type == sf::Event::KeyPressed && !tabs.empty()) { + if(((!tabs[selected_tab].body->can_move_left() || event.key.alt) && (event.key.code == sf::Keyboard::Left || (event.key.control && event.key.code == sf::Keyboard::H))) + || (event.key.code == sf::Keyboard::Tab && event.key.shift)) + { + if(selected_tab > 0) + move_selected_tab(selected_tab - 1); + } else if(((!tabs[selected_tab].body->can_move_right() || event.key.alt) && (event.key.code == sf::Keyboard::Right || (event.key.control && event.key.code == sf::Keyboard::L))) + || (event.key.code == sf::Keyboard::Tab && !event.key.shift)) + { + if(selected_tab < (int)tabs.size() - 1) + move_selected_tab(selected_tab + 1); + } else if(event.key.control && event.key.code >= sf::Keyboard::Num1 && event.key.code <= sf::Keyboard::Num9) { + const int tab_target = event.key.code - sf::Keyboard::Num1; + if(tab_target < (int)tabs.size()) + move_selected_tab(tab_target); } } } void Tabs::draw(sf::RenderWindow &window, sf::Vector2f pos, float width) { - if(width - tab_margin_x < 0.0f || tab_texts.empty()) return; + if(width - tab_margin_x < 0.0f || tabs.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))); + const int num_visible_tabs = std::min((int)tabs.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); @@ -80,7 +100,7 @@ namespace QuickMedia { 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); + float overflow_last = (scroll_fixed + tab_index_to_x_offset(tabs.size() - 1) + tab_background_width) - (width - tab_margin_x); if(overflow_last < 0.0f) scroll_fixed -= overflow_last; @@ -96,7 +116,7 @@ namespace QuickMedia { const auto start_pos = pos; pos.x += scroll_fixed; - for(size_t i = 0; i < tab_texts.size(); ++i) { + for(size_t i = 0; i < tabs.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) { @@ -112,7 +132,7 @@ namespace QuickMedia { background.draw(window); } - sf::Text &tab_text = tab_texts[index]; + sf::Text &tab_text = tabs[index].text; 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); @@ -183,16 +203,16 @@ namespace QuickMedia { } void Tabs::set_text(int index, const std::string &text) { - if(index < 0 || index >= (int)tab_texts.size() || text == tab_labels_utf8[index]) return; - tab_texts[index].setString(sf::String::fromUtf8(text.begin(), text.end())); - tab_labels_utf8[index] = text; + if(index < 0 || index >= (int)tabs.size() || text == tabs[index].label_utf8) return; + tabs[index].text.setString(sf::String::fromUtf8(text.begin(), text.end())); + tabs[index].label_utf8 = text; } void Tabs::set_selected(int index) { - if(tab_texts.empty()) { + if(tabs.empty()) { selected_tab = 0; } else { - selected_tab = std::min(std::max(index, 0), (int)tab_texts.size() - 1); + selected_tab = std::min(std::max(index, 0), (int)tabs.size() - 1); } } diff --git a/src/plugins/Fourchan.cpp b/src/plugins/Fourchan.cpp index d2f81a9..52024e1 100644 --- a/src/plugins/Fourchan.cpp +++ b/src/plugins/Fourchan.cpp @@ -167,7 +167,7 @@ namespace QuickMedia { } PluginResult FourchanBoardsPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) { - result_tabs.push_back(Tab{create_body(false), std::make_unique<FourchanThreadListPage>(program, title, url), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + result_tabs.push_back(Tab{create_body(false, true), std::make_unique<FourchanThreadListPage>(program, title, url), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); return PluginResult::OK; } @@ -262,6 +262,8 @@ namespace QuickMedia { if(author.isString()) author_str = author.asString(); + author_str += " #" + std::to_string(post_num.asInt64()); + std::string comment_text; extract_comment_pieces(sub_begin, sub_end - sub_begin, [&comment_text](const CommentPiece &cp) { diff --git a/src/plugins/Page.cpp b/src/plugins/Page.cpp index 9eb874f..8605d82 100644 --- a/src/plugins/Page.cpp +++ b/src/plugins/Page.cpp @@ -29,8 +29,8 @@ namespace QuickMedia { return DownloadResult::OK; } - std::unique_ptr<Body> Page::create_body(bool plain_text_list) { - return program->create_body(plain_text_list); + std::unique_ptr<Body> Page::create_body(bool plain_text_list, bool prefer_card_view) { + return program->create_body(plain_text_list, prefer_card_view); } std::unique_ptr<SearchBar> Page::create_search_bar(const std::string &placeholder_text, int search_delay) { diff --git a/src/plugins/Soundcloud.cpp b/src/plugins/Soundcloud.cpp index 9c2d5f4..f4d01b6 100644 --- a/src/plugins/Soundcloud.cpp +++ b/src/plugins/Soundcloud.cpp @@ -253,7 +253,7 @@ namespace QuickMedia { if(url == "track") { SoundcloudPlaylist *playlist = static_cast<SoundcloudPlaylist*>(submit_body_item->extra.get()); - auto body = create_body(); + auto body = create_body(false, true); body->items = playlist->tracks; result_tabs.push_back(Tab{std::move(body), std::make_unique<SoundcloudPlaylistPage>(program, playlist, title), nullptr}); } else if(url.find("/stream/users/") != std::string::npos) { @@ -263,7 +263,7 @@ namespace QuickMedia { DownloadResult result = download_json(json_root, query_url, {}, true); if(result != DownloadResult::OK) return download_result_to_plugin_result(result); - auto body = create_body(); + auto body = create_body(false, true); std::string next_href; PluginResult pr = parse_user_page(json_root, body->items, next_href); if(pr != PluginResult::OK) return pr; @@ -283,7 +283,7 @@ namespace QuickMedia { if(!url_json.isString()) return PluginResult::ERR; - result_tabs.push_back(Tab{create_body(), std::make_unique<SoundcloudAudioPage>(program, url_json.asString()), nullptr}); + result_tabs.push_back(Tab{nullptr, std::make_unique<SoundcloudAudioPage>(program, url_json.asString()), nullptr}); } return PluginResult::OK; diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp index f7b36d6..9b7c5ba 100644 --- a/src/plugins/Youtube.cpp +++ b/src/plugins/Youtube.cpp @@ -648,7 +648,7 @@ namespace QuickMedia { if(strncmp(url.c_str(), "https://www.youtube.com/channel/", 32) == 0) { // TODO: Make all pages (for all services) lazy fetch in a similar manner! - result_tabs.push_back(Tab{create_body(), std::make_unique<YoutubeChannelPage>(program, url, "", title), create_search_bar("Search...", 350)}); + result_tabs.push_back(Tab{create_body(false, true), std::make_unique<YoutubeChannelPage>(program, url, "", title), create_search_bar("Search...", 350)}); } else { result_tabs.push_back(Tab{nullptr, std::make_unique<YoutubeVideoPage>(program, url), nullptr}); } |