From 59e4ce5c35e7e5a73d61e7111e8d4f07d63b7056 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Fri, 4 Jun 2021 19:22:04 +0200 Subject: Add grid view --- src/Body.cpp | 699 ++++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 480 insertions(+), 219 deletions(-) (limited to 'src/Body.cpp') 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(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(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(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(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 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; -- cgit v1.2.3