aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md3
-rw-r--r--TODO5
-rw-r--r--include/Body.hpp25
-rw-r--r--include/QuickMedia.hpp2
-rw-r--r--include/RoundedRectangle.hpp6
-rw-r--r--include/Tabs.hpp13
-rw-r--r--plugins/Page.hpp2
-rw-r--r--shaders/rounded_rectangle.glsl6
-rw-r--r--src/Body.cpp699
-rw-r--r--src/Entry.cpp2
-rw-r--r--src/QuickMedia.cpp60
-rw-r--r--src/RoundedRectangle.cpp13
-rw-r--r--src/SearchBar.cpp5
-rw-r--r--src/Tabs.cpp92
-rw-r--r--src/plugins/Fourchan.cpp4
-rw-r--r--src/plugins/Page.cpp4
-rw-r--r--src/plugins/Soundcloud.cpp6
-rw-r--r--src/plugins/Youtube.cpp2
18 files changed, 625 insertions, 324 deletions
diff --git a/README.md b/README.md
index b205b22..5c7996b 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,8 @@ EXAMPLES:
If you are running arch linux then you can install QuickMedia from aur (https://aur.archlinux.org/packages/quickmedia-git/), otherwise you will need to first install [sibs](https://git.dec05eba.com/sibs/) and then run `./install.sh` as root.
## Controls
Press `Arrow up` / `Arrow down` or `Ctrl+K` / `Ctrl+J` to navigate the menu and also to scroll to the previous/next image when viewing manga in scroll mode. Alternatively you can use the mouse scroll to scroll to the previous/next manga in scroll mode.\
-Press `Arrow left` / `Arrow right` or `Ctrl+H` / `Ctrl+L` to switch tab.\
+Press `Arrow left` / `Arrow right` or `Ctrl+H` / `Ctrl+L` or `Tab` / `Shift+Tab` to switch tab. The alt key can also be used in grid mode to move tabs instead of moving selection left/right.\
+Press `Ctrl+1-9` to move to the numbered tab.\
Press `Page up` to scroll up and entire page or `Page down` to scroll down an entire page.\
Press `Home` to scroll to the top or `End` to scroll to the bottom.\
Press `Enter` (aka `Return`) to select the item.\
diff --git a/TODO b/TODO
index ca9334d..088511e 100644
--- a/TODO
+++ b/TODO
@@ -1,5 +1,4 @@
Give user the option to start where they left off or from the start or from the start (for manga).
-Add grid-view when thumbnails are visible.
Add scrollbar.
Somehow deal with youtube banning ip when searching too often.
When continuing to read manga from a different page from the first and there is no cache for the chapter, then start downloading from the current page instead of page 1.
@@ -94,7 +93,6 @@ If manga page fails to download then show "failed to download image" as text and
Use <img src to add custom emojis, and add setting for adding/removing custom emoji.
Set window title when room name changes in matrix.
Create multiple BodyItem types. BodyItem has a lot of fields and most of them are not always used.
-Add grid view to matrix and navigate between them using alt+arrow keys.
Remove display names from reactions if there are many reactions, and instead group them into: reaction (#number of this type of reaction); for example: 👍 2.
Make reaction and deleted message provisional.
Allow removing reactions.
@@ -149,4 +147,5 @@ Ctrl+S when a body item is selected on youtube/xxxplugins should show an option
Show a subscribe button for channels, which should be red and say "subscribe" if we are not subscribed and it should be gray and say "unsubscribe" if we already are subscribed.
Display youtube playlists differently and support downloading the whole playlist at once.
To fix jitter in body when items are added before the selected item, we should increase page_scroll by the height of the new item.
-Cancel current search when search is updated. \ No newline at end of file
+Cancel current search when search is updated.
+Support 4chan archive. \ No newline at end of file
diff --git a/include/Body.hpp b/include/Body.hpp
index 4846b59..4007bc4 100644
--- a/include/Body.hpp
+++ b/include/Body.hpp
@@ -198,10 +198,10 @@ namespace QuickMedia {
bool select_next_page();
// Select previous item, ignoring invisible items. Returns true if the item was changed or if the item scrolled. This can be used to check if the top was hit when wrap_around is set to false
- bool select_previous_item(bool scroll_page_if_large_item = false);
+ bool select_previous_item(bool scroll_page_if_large_item = false, bool ignore_columns = false);
// Select next item, ignoring invisible items. Returns true if the item was changed or if the item scrolled. This can be used to check if the bottom was hit when wrap_around is set to false
- bool select_next_item(bool scroll_page_if_large_item = false);
+ bool select_next_item(bool scroll_page_if_large_item = false, bool ignore_columns = false);
void set_selected_item(int item, bool reset_prev_selected_item = true);
void reset_prev_selected_item();
@@ -259,6 +259,9 @@ namespace QuickMedia {
void apply_search_filter_for_item(BodyItem *body_item);
+ bool can_move_left() const { return selected_column > 0; }
+ bool can_move_right() const { return selected_column + 1 < num_columns; }
+
BodyItems items;
bool draw_thumbnails;
// Set to {0, 0} to disable resizing
@@ -271,13 +274,17 @@ namespace QuickMedia {
bool title_mark_urls = false;
bool swiping_enabled = false;
bool show_drop_shadow = true;
+ bool card_view = false;
std::function<void()> on_top_reached = nullptr;
std::function<void()> on_bottom_reached = nullptr;
private:
void draw_drop_shadow(sf::RenderWindow &window);
void filter_search_fuzzy_item(const std::string &text, BodyItem *body_item);
- void handle_item_render(BodyItem *item, const sf::Vector2f pos, const sf::Vector2f size, const float item_height, int item_index);
+ void handle_item_render(const sf::Vector2f pos, const float item_width, const float item_height, int item_index);
+ // Returns the the body total drawn height
+ float draw_list_view(sf::RenderWindow &window, sf::Vector2f pos, sf::Vector2f size, const int prev_num_visible_items, const Json::Value &content_progress);
+ void draw_card_view(sf::RenderWindow &window, sf::Vector2f pos, sf::Vector2f size, sf::Vector2u window_size, float scissor_y);
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);
@@ -312,6 +319,7 @@ namespace QuickMedia {
double page_scroll = 0.0;
float selected_scrolled = 0.0f;
// TODO: Use a loading gif or something similar instead, to properly indicate the image is loading. Which could also have text that says "failed to load image" when image loading failed.
+ // TODO: Use rounded rectangle instead
sf::RectangleShape image_fallback;
//sf::RectangleShape item_separator;
sf::Sprite image;
@@ -336,6 +344,9 @@ namespace QuickMedia {
bool mouse_left_clicked = false;
bool has_scrolled_with_input = false;
bool click_counts = false;
+ int num_columns = 1;
+ int selected_column = 0;
+ bool card_view_enabled = true;
StuckDirection stuck_direction = StuckDirection::NONE;
sf::Vector2f mouse_click_pos;
sf::Vector2f mouse_release_pos;
@@ -354,10 +365,10 @@ namespace QuickMedia {
RoundedRectangle item_background;
RoundedRectangle reaction_background;
bool render_selected_item_bg = true;
- float item_background_prev_pos_y = 0.0f;
- float item_background_target_pos_y = 0.0f;
- float item_background_prev_height = 0.0f;
- float item_background_target_height = 0.0f;
+ sf::Vector2f item_background_prev_pos;
+ sf::Vector2f item_background_target_pos;
+ sf::Vector2f item_background_prev_size;
+ sf::Vector2f item_background_target_size;
TargetSetState target_y_set = TargetSetState::NOT_SET;
// TODO: Instead of using this, add functions for modifying |items| and apply the filter on those new items
DirtyState items_dirty = DirtyState::FALSE;
diff --git a/include/QuickMedia.hpp b/include/QuickMedia.hpp
index 09b0806..903e815 100644
--- a/include/QuickMedia.hpp
+++ b/include/QuickMedia.hpp
@@ -79,7 +79,7 @@ namespace QuickMedia {
~Program();
int run(int argc, char **argv);
- std::unique_ptr<Body> create_body(bool plain_text_list = false);
+ std::unique_ptr<Body> create_body(bool plain_text_list = false, bool prefer_card_view = false);
std::unique_ptr<SearchBar> create_search_bar(const std::string &placeholder, int search_delay);
bool load_manga_content_storage(const char *service_name, const std::string &manga_title, const std::string &manga_id);
diff --git a/include/RoundedRectangle.hpp b/include/RoundedRectangle.hpp
index b9d0754..2106634 100644
--- a/include/RoundedRectangle.hpp
+++ b/include/RoundedRectangle.hpp
@@ -17,7 +17,7 @@ namespace QuickMedia {
void set_color(sf::Color color);
sf::Vector2f get_position() const;
sf::Vector2f get_size() const;
- void set_band(float y, float height);
+ void set_band(sf::Vector2f pos, sf::Vector2f size);
void set_band_color(sf::Color color);
void draw(sf::RenderTarget &target);
private:
@@ -26,8 +26,8 @@ namespace QuickMedia {
sf::Vector2f size;
sf::Vertex vertices[4];
sf::Shader *rounded_rectangle_shader;
- float band_y;
- float band_height;
+ sf::Vector2f band_pos;
+ sf::Vector2f band_size;
sf::Color band_color;
};
} \ No newline at end of file
diff --git a/include/Tabs.hpp b/include/Tabs.hpp
index ec5ab96..f7c3054 100644
--- a/include/Tabs.hpp
+++ b/include/Tabs.hpp
@@ -13,6 +13,8 @@ namespace sf {
}
namespace QuickMedia {
+ class Body;
+
class Tabs {
public:
Tabs(sf::Shader *rounded_rectangle_shader, sf::Color shade_color = sf::Color(33, 37, 44));
@@ -21,7 +23,7 @@ namespace QuickMedia {
static float get_shade_height();
// Returns the id (index) of the tab. The ids start from 0
- int add_tab(const std::string &title);
+ int add_tab(const std::string &title, Body *body);
void on_event(sf::Event &event);
void draw(sf::RenderWindow &window, sf::Vector2f pos, float width);
@@ -32,10 +34,15 @@ namespace QuickMedia {
std::function<void(int)> on_change_tab = nullptr;
private:
+ void move_selected_tab(int new_tab);
float tab_index_to_x_offset(int index);
private:
- std::vector<sf::Text> tab_texts;
- std::vector<std::string> tab_labels_utf8;
+ struct Tab {
+ sf::Text text;
+ std::string label_utf8; // TODO: Remove
+ Body *body;
+ };
+ std::vector<Tab> tabs;
RoundedRectangle background;
sf::RectangleShape shade;
int selected_tab = 0;
diff --git a/plugins/Page.hpp b/plugins/Page.hpp
index 6a3ea9f..54d7d88 100644
--- a/plugins/Page.hpp
+++ b/plugins/Page.hpp
@@ -57,7 +57,7 @@ namespace QuickMedia {
// This is called both when first navigating to page and when going back to page
virtual void on_navigate_to_page(Body *body) { (void)body; }
- std::unique_ptr<Body> create_body(bool plain_text_list = false);
+ std::unique_ptr<Body> create_body(bool plain_text_list = false, bool prefer_card_view = false);
std::unique_ptr<SearchBar> create_search_bar(const std::string &placeholder_text, int search_delay);
bool load_manga_content_storage(const char *service_name, const std::string &manga_title, const std::string &manga_id);
diff --git a/shaders/rounded_rectangle.glsl b/shaders/rounded_rectangle.glsl
index 2753383..88a057a 100644
--- a/shaders/rounded_rectangle.glsl
+++ b/shaders/rounded_rectangle.glsl
@@ -1,6 +1,6 @@
uniform float radius;
-uniform float band_y;
-uniform float band_height;
+uniform vec2 band_pos;
+uniform vec2 band_size;
uniform vec4 band_color;
uniform vec2 resolution;
@@ -15,7 +15,7 @@ void main() {
vec4 background_color = vec4(0.0, 0.0, 0.0, 0.0);
float a = clamp(rounded_rect(uv - center, size - radius, radius), 0.0, 1.0);
vec4 front_color = gl_Color;
- if(uv.y >= band_y && uv.y <= band_y + band_height)
+ if(uv.x >= band_pos.x && uv.x <= band_pos.x + band_size.x && uv.y >= band_pos.y && uv.y <= band_pos.y + band_size.y)
front_color = band_color;
gl_FragColor = mix(front_color, background_color, a);
} \ No newline at end of file
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});
}