aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--TODO1
-rw-r--r--include/Body.hpp9
-rw-r--r--src/Body.cpp103
-rw-r--r--src/QuickMedia.cpp23
4 files changed, 113 insertions, 23 deletions
diff --git a/TODO b/TODO
index 5798914..3c3ca29 100644
--- a/TODO
+++ b/TODO
@@ -37,7 +37,6 @@ Add option to disable autosearch and search when pressing enter instead or somet
Sleep when idle, to reduce cpu usage from 1-2% to 0%, important for mobile devices. Also render view to a rendertexture and render that instead of redrawing every time every time.
Provide a way to specify when notifications should be received (using matrix api) and also read the notification config from matrix. Also provide a way to disable notifications globally.
Use quickmedia to show image in matrix rooms, instead of mpv.
-Merge body items in matrix if they are posted by the same author (there is a git stash for this).
Add command to ban users.
Support peertube (works with mpv, but need to implement search and related videos).
Scroll to bottom when receiving a new message even if the selected message is not the last one. It should instead scroll if the last message is visible on the screen.
diff --git a/include/Body.hpp b/include/Body.hpp
index 53622e8..738c41b 100644
--- a/include/Body.hpp
+++ b/include/Body.hpp
@@ -160,6 +160,8 @@ namespace QuickMedia {
using BodyItems = std::vector<std::shared_ptr<BodyItem>>;
using BodyItemRenderCallback = std::function<void(BodyItem *body_item)>;
+ // Return true to merge
+ using BodyItemMergeHandler = std::function<bool(BodyItem *prev_item, BodyItem *this_item)>;
enum class AttachSide {
TOP,
@@ -216,7 +218,7 @@ namespace QuickMedia {
// because of Text::setMaxWidth
void draw_item(sf::RenderWindow &window, BodyItem *item, sf::Vector2f pos, sf::Vector2f size, bool include_embedded_item = true, bool is_embedded = false);
- float get_item_height(BodyItem *item, float width, bool load_texture = true, bool include_embedded_item = true);
+ float get_item_height(BodyItem *item, float width, bool load_texture = true, bool include_embedded_item = true, bool merge_with_previous = false);
float get_spacing_y() const;
static bool string_find_case_insensitive(const std::string &str, const std::string &substr);
@@ -249,14 +251,17 @@ namespace QuickMedia {
sf::Vector2i thumbnail_max_size;
sf::Color line_separator_color;
BodyItemRenderCallback body_item_render_callback;
+ BodyItemMergeHandler body_item_merge_handler;
std::function<void(BodyItem*)> body_item_select_callback;
sf::Shader *thumbnail_mask_shader;
AttachSide attach_side = AttachSide::TOP;
private:
- void draw_item(sf::RenderWindow &window, BodyItem *item, const sf::Vector2f &pos, const sf::Vector2f &size, const float item_height, const int item_index, const Json::Value &content_progress, bool include_embedded_item = true);
+ void draw_item(sf::RenderWindow &window, BodyItem *item, const sf::Vector2f &pos, const sf::Vector2f &size, const float item_height, const int item_index, const Json::Value &content_progress, bool include_embedded_item = true, bool merge_with_previous = false);
void update_dirty_state(BodyItem *body_item, float width);
void clear_body_item_cache(BodyItem *body_item);
sf::Vector2i get_item_thumbnail_size(BodyItem *item) const;
+ BodyItem* get_previous_visible_item(int start_index);
+ BodyItem* get_next_visible_item(int start_index);
private:
Program *program;
std::unordered_map<std::string, std::shared_ptr<ThumbnailData>> item_thumbnail_textures;
diff --git a/src/Body.cpp b/src/Body.cpp
index a34fb7d..ee84f5f 100644
--- a/src/Body.cpp
+++ b/src/Body.cpp
@@ -534,9 +534,15 @@ namespace QuickMedia {
if(selected_item_diff > 0) {
int num_items_scrolled = 0;
int i = prev_selected_item;
+ BodyItem *prev_body_item = get_previous_visible_item(i);
while(num_items_scrolled < selected_int_diff_abs && i < num_items) {
if(items[i]->visible) {
- page_scroll += (get_item_height(items[i].get(), size.x, selected_int_diff_abs < 50) + spacing_y);
+ const bool merge_with_previous = body_item_merge_handler && body_item_merge_handler(prev_body_item, items[i].get());
+ page_scroll += get_item_height(items[i].get(), size.x, selected_int_diff_abs < 50, true, merge_with_previous);
+ if(merge_with_previous)
+ page_scroll -= spacing_y;
+ page_scroll += spacing_y;
+ prev_body_item = items[i].get();
}
++num_items_scrolled;
++i;
@@ -545,9 +551,15 @@ namespace QuickMedia {
} else if(selected_item_diff < 0) {
int num_items_scrolled = 0;
int i = prev_selected_item - 1;
+ BodyItem *prev_body_item = get_previous_visible_item(i);
while(num_items_scrolled < selected_int_diff_abs && i >= 0) {
if(items[i]->visible) {
- page_scroll -= (get_item_height(items[i].get(), size.x, selected_int_diff_abs < 50) + spacing_y);
+ prev_body_item = get_previous_visible_item(i);
+ const bool merge_with_previous = body_item_merge_handler && body_item_merge_handler(prev_body_item, items[i].get());
+ page_scroll -= get_item_height(items[i].get(), size.x, selected_int_diff_abs < 50, true, merge_with_previous);
+ if(merge_with_previous)
+ page_scroll += spacing_y;
+ page_scroll -= spacing_y;
}
++num_items_scrolled;
--i;
@@ -555,7 +567,9 @@ namespace QuickMedia {
prev_selected_item = selected_item;
}
- selected_item_height = get_item_height(items[selected_item].get(), size.x) + spacing_y;
+ const bool merge_with_previous = body_item_merge_handler && body_item_merge_handler(get_previous_visible_item(selected_item), items[selected_item].get());
+ selected_item_height = get_item_height(items[selected_item].get(), size.x, true, true, merge_with_previous);
+ selected_item_height += spacing_y;
bool selected_item_fits_on_screen = selected_item_height <= size.y;
selected_line_top_visible = pos.y - start_y + page_scroll >= 0.0f;
selected_line_bottom_visible = pos.y - start_y + page_scroll + selected_item_height <= size.y;
@@ -584,11 +598,20 @@ namespace QuickMedia {
}
if(page_scroll > size.y - selected_item_height && selected_item_fits_on_screen) {
- //fprintf(stderr, "top!\n");
+ //fprintf(stderr, "top: %f\n", page_scroll - (size.y - selected_item_height));
page_scroll = size.y - selected_item_height;
+ if(merge_with_previous)
+ page_scroll += spacing_y*2.0f;
+
+ BodyItem *next_body_item = get_next_visible_item(selected_item);
+ const bool merge_with_next = next_body_item && body_item_merge_handler && body_item_merge_handler(items[selected_item].get(), next_body_item);
+ if(merge_with_next)
+ page_scroll += spacing_y;
} else if(page_scroll < 0.0f && selected_line_top_visible && selected_item_fits_on_screen) {
//fprintf(stderr, "bottom!\n");
page_scroll = 0.0f;
+ if(merge_with_previous)
+ page_scroll += spacing_y;
}
}
@@ -610,9 +633,14 @@ namespace QuickMedia {
if(!item->visible)
continue;
+ BodyItem *prev_body_item = get_previous_visible_item(i);
+ const bool merge_with_previous = body_item_merge_handler && body_item_merge_handler(prev_body_item, item.get());
+
item->last_drawn_time = elapsed_time_sec;
- float item_height = get_item_height(item.get(), size.x);
- prev_pos.y -= (item_height + spacing_y);
+ float item_height = get_item_height(item.get(), size.x, true, true, merge_with_previous);
+ float item_height_with_merge = item_height;
+ item_height_with_merge += spacing_y;
+ prev_pos.y -= item_height_with_merge;
if(prev_pos.y < start_y) {
items_cut_off = true;
@@ -620,22 +648,28 @@ namespace QuickMedia {
first_item_fully_visible = false;
}
- if(prev_pos.y + item_height + spacing_y <= start_y)
+ if(prev_pos.y + item_height_with_merge <= start_y)
break;
// This is needed here rather than above the loop, since update_dirty_text cant be called inside scissor because it corrupts the text for some reason
glEnable(GL_SCISSOR_TEST);
glScissor(scissor_pos.x, (int)window_size.y - (int)scissor_pos.y - (int)scissor_size.y, scissor_size.x, scissor_size.y);
- draw_item(window, item.get(), prev_pos, size, item_height, i, content_progress);
+ draw_item(window, item.get(), prev_pos, size, item_height, i, content_progress, true, merge_with_previous);
glDisable(GL_SCISSOR_TEST);
++num_visible_items;
if(first_item_fully_visible)
first_fully_visible_item = i;
+
+ if(merge_with_previous)
+ prev_pos.y += spacing_y;
}
offset_to_top = prev_pos.y - start_y;
+ BodyItem *prev_body_item = get_previous_visible_item(selected_item);
+ float prev_item_height = 0.0f;
+
sf::Vector2f after_pos = pos;
for(int i = selected_item; i < num_items; ++i) {
auto &item = items[i];
@@ -644,6 +678,10 @@ namespace QuickMedia {
if(!item->visible)
continue;
+ const bool merge_with_previous = body_item_merge_handler && body_item_merge_handler(prev_body_item, item.get());
+ if(merge_with_previous)
+ after_pos.y -= spacing_y;
+
if(after_pos.y < start_y) {
items_cut_off = true;
first_item_fully_visible = false;
@@ -660,14 +698,15 @@ namespace QuickMedia {
}
item->last_drawn_time = elapsed_time_sec;
- float item_height = get_item_height(item.get(), size.x);
+ float item_height = get_item_height(item.get(), size.x, true, true, merge_with_previous);
// This is needed here rather than above the loop, since update_dirty_text cant be called inside scissor because it corrupts the text for some reason
glEnable(GL_SCISSOR_TEST);
glScissor(scissor_pos.x, (int)window_size.y - (int)scissor_pos.y - (int)scissor_size.y, scissor_size.x, scissor_size.y);
- draw_item(window, item.get(), after_pos, size, item_height, i, content_progress);
+ draw_item(window, item.get(), after_pos, size, item_height, i, content_progress, true, merge_with_previous);
glDisable(GL_SCISSOR_TEST);
- after_pos.y += item_height + spacing_y;
+ after_pos.y += item_height;
+ after_pos.y += spacing_y;
++num_visible_items;
if(after_pos.y - start_y > size.y) {
@@ -681,6 +720,9 @@ namespace QuickMedia {
if(first_item_fully_visible && first_fully_visible_item == -1)
first_fully_visible_item = i;
+
+ prev_body_item = items[i].get();
+ prev_item_height = item_height;
}
if(first_fully_visible_item == -1)
@@ -808,6 +850,24 @@ namespace QuickMedia {
return content_size;
}
+ // TODO: Cache, and take into consideration updated items and visibility change
+ BodyItem* Body::get_previous_visible_item(int start_index) {
+ for(int i = start_index - 1; i >= 0; --i) {
+ if(items[i]->visible)
+ return items[i].get();
+ }
+ return nullptr;
+ }
+
+ // TODO: Cache, and take into consideration updated items and visibility change
+ BodyItem* Body::get_next_visible_item(int start_index) {
+ for(int i = start_index + 1; i < (int)items.size(); ++i) {
+ if(items[i]->visible)
+ return items[i].get();
+ }
+ return nullptr;
+ }
+
static sf::Vector2f to_vec2f(const sf::Vector2i &vec) {
return sf::Vector2f(vec.x, vec.y);
}
@@ -843,7 +903,7 @@ namespace QuickMedia {
return "";
}
- 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) {
+ 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) {
// TODO: Instead of generating a new hash everytime to access textures, cache the hash of the thumbnail url
std::shared_ptr<ThumbnailData> item_thumbnail;
if(draw_thumbnails && !item->thumbnail_url.empty()) {
@@ -884,7 +944,7 @@ namespace QuickMedia {
}
float text_offset_x = padding_x;
- if(draw_thumbnails && item_thumbnail) {
+ if(draw_thumbnails && item_thumbnail && !merge_with_previous) {
double elapsed_time_thumbnail = 0.0;
if(item_thumbnail->loading_state == LoadingState::APPLIED_TO_TEXTURE)
elapsed_time_thumbnail = item_thumbnail->texture_applied_time.getElapsedTime().asSeconds(); //thumbnail_fade_duration_sec
@@ -943,10 +1003,13 @@ namespace QuickMedia {
window.draw(loading_icon);
text_offset_x += image_padding_x + content_size.x;
}
+ } else if(merge_with_previous && !item->thumbnail_url.empty()) {
+ // TODO: Get the previous items text_offset_x instead of + 32.0f * get_ui_scale()
+ text_offset_x += image_padding_x + (32.0f * get_ui_scale());
}
const float timestamp_text_y = std::floor(item_pos.y + padding_y - std::floor(6.0f * get_ui_scale()));
- if(item->author_text) {
+ 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 - 6.0f * get_ui_scale()));
item->author_text->setMaxWidth(size.x - text_offset_x - image_padding_x);
item->author_text->draw(window);
@@ -1056,10 +1119,10 @@ namespace QuickMedia {
}
}
- float Body::get_item_height(BodyItem *item, float width, bool load_texture, bool include_embedded_item) {
+ float Body::get_item_height(BodyItem *item, float width, bool load_texture, bool include_embedded_item, bool merge_with_previous) {
float image_height = 0.0f;
float text_offset_x = padding_x;
- if(draw_thumbnails && !item->thumbnail_url.empty()) {
+ if(draw_thumbnails && !item->thumbnail_url.empty() && !merge_with_previous) {
sf::Vector2i content_size = get_item_thumbnail_size(item);
image_height = content_size.y;
@@ -1100,6 +1163,9 @@ namespace QuickMedia {
} else {
text_offset_x += image_padding_x + content_size.x;
}
+ } else if(merge_with_previous) {
+ // TODO: Get the previous items text_offset_x instead of + 32.0f * get_ui_scale()
+ text_offset_x += image_padding_x + (32.0f * get_ui_scale());
}
if(load_texture)
@@ -1109,7 +1175,7 @@ namespace QuickMedia {
if(item->title_text) {
item_height += item->title_text->getHeight() - 2.0f + std::floor(3.0f * get_ui_scale());
}
- if(item->author_text) {
+ if(item->author_text && !merge_with_previous) {
item_height += item->author_text->getHeight() - 2.0f + std::floor(3.0f * get_ui_scale());
}
if(include_embedded_item && item->embedded_item_status != FetchStatus::NONE) {
@@ -1142,7 +1208,8 @@ namespace QuickMedia {
}
item_height = std::max(item_height, image_height);
- return item_height + padding_y * 2.0f;
+ item_height += (padding_y * 2.0f);
+ return item_height;
}
float Body::get_spacing_y() const {
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index 7835af2..f020277 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -3706,7 +3706,7 @@ namespace QuickMedia {
message->type = MessageType::TEXT;
message->timestamp = time(NULL) * 1000;
- const sf::Color provisional_message_color(255, 255, 255, 180);
+ const sf::Color provisional_message_color(255, 255, 255, 150);
int num_items = tabs[MESSAGES_TAB_INDEX].body->items.size();
bool scroll_to_end = num_items == 0;
@@ -3990,6 +3990,24 @@ namespace QuickMedia {
};
};
+ tabs[MESSAGES_TAB_INDEX].body->body_item_merge_handler = [](BodyItem *prev_item, BodyItem *this_item) {
+ Message *message = static_cast<Message*>(this_item->userdata);
+ if(!message || !prev_item || !prev_item->userdata)
+ return false;
+
+ if(is_visual_media_message_type(message->type) && !this_item->thumbnail_url.empty())
+ return false;
+
+ Message *prev_message = static_cast<Message*>(prev_item->userdata);
+ if(is_visual_media_message_type(prev_message->type) && !prev_item->thumbnail_url.empty())
+ return false;
+
+ if(message->user == prev_message->user)
+ return true;
+
+ return false;
+ };
+
const float tab_spacer_height = 0.0f;
sf::Vector2f body_pos;
sf::Vector2f body_size;
@@ -4884,7 +4902,8 @@ namespace QuickMedia {
glScissor(0.0f, 0.0f, this->body_size.x, window_size.y);
window.draw(room_list_background);
window.draw(room_label);
- room_tabs[room_selected_tab].body->draw(window, sf::Vector2f(0.0f, tab_y), sf::Vector2f(this->body_size.x, window_size.y - tab_y), Json::Value::nullSingleton());
+ const float padding_x = std::floor(10.0f * get_ui_scale());
+ room_tabs[room_selected_tab].body->draw(window, sf::Vector2f(padding_x, tab_y), sf::Vector2f(this->body_size.x - padding_x * 2.0f, window_size.y - tab_y), Json::Value::nullSingleton());
glDisable(GL_SCISSOR_TEST);
}