diff options
Diffstat (limited to 'src/QuickMedia.cpp')
-rw-r--r-- | src/QuickMedia.cpp | 433 |
1 files changed, 433 insertions, 0 deletions
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp new file mode 100644 index 0000000..a63f430 --- /dev/null +++ b/src/QuickMedia.cpp @@ -0,0 +1,433 @@ +#include "../include/QuickMedia.hpp" +#include "../plugins/Manganelo.hpp" +#include "../plugins/Youtube.hpp" +#include "../include/VideoPlayer.hpp" + +#include <SFML/Graphics/RectangleShape.hpp> +#include <SFML/Graphics/Text.hpp> +#include <SFML/Window/Event.hpp> +#include <assert.h> + +const sf::Color front_color(43, 45, 47); +const sf::Color back_color(33, 35, 37); + +namespace QuickMedia { + class Body { + public: + Body(sf::Font &font) : title_text("", font, 14), selected_item(0) { + title_text.setFillColor(sf::Color::White); + } + + void add_item(std::unique_ptr<BodyItem> item) { + items.push_back(std::move(item)); + } + + // Select previous item, ignoring invisible items + void select_previous_item() { + if(items.empty()) + return; + + int num_items = (int)items.size(); + for(int i = 0; i < num_items; ++i) { + --selected_item; + if(selected_item < 0) + selected_item = num_items - 1; + if(items[selected_item]->visible) + return; + } + } + + // Select next item, ignoring invisible items + void select_next_item() { + if(items.empty()) + return; + + int num_items = (int)items.size(); + for(int i = 0; i < num_items; ++i) { + ++selected_item; + if(selected_item == num_items) + selected_item = 0; + if(items[selected_item]->visible) + return; + } + } + + void reset_selected() { + selected_item = 0; + } + + void clear_items() { + items.clear(); + } + + BodyItem* get_selected() const { + if(items.empty() || !items[selected_item]->visible) + return nullptr; + return items[selected_item].get(); + } + + void clamp_selection() { + int num_items = (int)items.size(); + if(items.empty()) + return; + + if(selected_item < 0) + selected_item = 0; + else if(selected_item >= num_items) + selected_item = num_items - 1; + + for(int i = selected_item; i >= 0; --i) { + if(items[i]->visible) { + selected_item = i; + return; + } + } + + for(int i = selected_item; i < num_items; ++i) { + if(items[i]->visible) { + selected_item = i; + return; + } + } + } + + void draw(sf::RenderWindow &window, sf::Vector2f pos, sf::Vector2f size) { + const float font_height = title_text.getCharacterSize() + 8.0f; + const float image_height = 50.0f; + + sf::RectangleShape image(sf::Vector2f(50, image_height)); + image.setFillColor(sf::Color::White); + + sf::RectangleShape item_background; + item_background.setFillColor(front_color); + item_background.setOutlineThickness(1.0f); + item_background.setOutlineColor(sf::Color(63, 65, 67)); + + sf::RectangleShape selected_border(sf::Vector2f(5.0f, 50)); + selected_border.setFillColor(sf::Color::Red); + + int i = 0; + for(const auto &item : items) { + if(!item->visible) { + ++i; + continue; + } + + sf::Vector2f item_pos = pos; + if(i == selected_item) { + selected_border.setPosition(pos); + window.draw(selected_border); + item_pos.x += selected_border.getSize().x; + item_background.setFillColor(front_color); + } else { + item_background.setFillColor(sf::Color(38, 40, 42)); + } + + item_background.setPosition(item_pos); + item_background.setSize(sf::Vector2f(size.x, 50)); + window.draw(item_background); + + image.setPosition(item_pos); + window.draw(image); + + title_text.setString(item->title); + title_text.setPosition(item_pos.x + 50 + 10, item_pos.y); + window.draw(title_text); + + + pos.y += 50 + 10; + ++i; + } + } + + static bool string_find_case_insensitive(const std::string &str, const std::string &substr) { + auto it = std::search(str.begin(), str.end(), substr.begin(), substr.end(), + [](char c1, char c2) { + return std::toupper(c1) == std::toupper(c2); + }); + return it != str.end(); + } + + // TODO: Make this actually fuzzy... Right now it's just a case insensitive string find. + // TODO: Highlight the part of the text that matches the search + void filter_search_fuzzy(const std::string &text) { + if(text.empty()) { + for(auto &item : items) { + item->visible = true; + } + return; + } + + for(auto &item : items) { + item->visible = string_find_case_insensitive(item->title, text); + } + } + + sf::Text title_text; + int selected_item; + std::vector<std::unique_ptr<BodyItem>> items; + }; + + Program::Program() : + window(sf::VideoMode(800, 600), "QuickMedia"), + window_size(800, 600), + body(nullptr), + current_plugin(nullptr), + current_page(Page::SEARCH_SUGGESTION) + { + window.setVerticalSyncEnabled(true); + if(!font.loadFromFile("fonts/Lato-Regular.ttf")) { + fprintf(stderr, "Failed to load font!\n"); + abort(); + } + body = new Body(font); + //current_plugin = new Manganelo(); + current_plugin = new Youtube(); + search_bar = std::make_unique<SearchBar>(font); + page_view_stack.push(current_page); + } + + Program::~Program() { + delete body; + delete current_plugin; + } + + static SearchResult search_selected_suggestion(Body *body, Plugin *plugin) { + BodyItem *selected_item = body->get_selected(); + if(!selected_item) + return SearchResult::ERR; + + std::string selected_item_title = selected_item->title; + body->clear_items(); + SearchResult search_result = plugin->search(selected_item_title, body->items); + body->reset_selected(); + return search_result; + } + + static void update_search_suggestions(const sf::String &text, Body *body, Plugin *plugin) { + body->clear_items(); + if(text.isEmpty()) + return; + + body->items.push_back(std::make_unique<BodyItem>(text)); + SuggestionResult suggestion_result = plugin->update_search_suggestions(text, body->items); + body->clamp_selection(); + } + + void Program::run() { + while(window.isOpen()) { + switch(current_page) { + case Page::EXIT: + return; + case Page::SEARCH_SUGGESTION: + search_suggestion_page(); + break; + case Page::SEARCH_RESULT: + search_result_page(); + break; + case Page::VIDEO_CONTENT: + video_content_page(); + break; + default: + return; + } + } + } + + void Program::base_event_handler(sf::Event &event) { + + } + + void Program::search_suggestion_page() { + search_bar->onTextUpdateCallback = [this](const std::string &text) { + update_search_suggestions(text, body, current_plugin); + }; + + search_bar->onTextSubmitCallback = [this](const std::string &text) { + if(search_selected_suggestion(body, current_plugin) == SearchResult::OK) { + current_page = Page::SEARCH_RESULT; + page_view_stack.push(current_page); + } + }; + + sf::Vector2f body_pos; + sf::Vector2f body_size; + bool resized = true; + + while (window.isOpen() && current_page == Page::SEARCH_SUGGESTION) { + sf::Event event; + while (window.pollEvent(event)) { + if (event.type == sf::Event::Closed) { + window.close(); + current_page = Page::EXIT; + } else if(event.type == sf::Event::Resized) { + window_size.x = event.size.width; + window_size.y = event.size.height; + sf::FloatRect visible_area(0, 0, window_size.x, window_size.y); + window.setView(sf::View(visible_area)); + resized = true; + } else if(event.type == sf::Event::KeyPressed) { + if(event.key.code == sf::Keyboard::Up) { + body->select_previous_item(); + } else if(event.key.code == sf::Keyboard::Down) { + body->select_next_item(); + } else if(event.key.code == sf::Keyboard::Escape) { + current_page = Page::EXIT; + window.close(); + } + } else if(event.type == sf::Event::TextEntered) { + search_bar->onTextEntered(event.text.unicode); + } + } + + if(resized) { + search_bar->onWindowResize(window_size); + + float body_padding_horizontal = 50.0f; + float body_padding_vertical = 50.0f; + float body_width = window_size.x - body_padding_horizontal * 2.0f; + if(body_width < 400) { + body_width = window_size.x; + body_padding_horizontal = 0.0f; + } + + float search_bottom = search_bar->getBottom(); + body_pos = sf::Vector2f(body_padding_horizontal, search_bottom + body_padding_vertical); + body_size = sf::Vector2f(body_width, window_size.y); + } + + search_bar->update(); + + window.clear(back_color); + body->draw(window, body_pos, body_size); + search_bar->draw(window); + window.display(); + } + } + + void Program::search_result_page() { + search_bar->onTextUpdateCallback = [this](const std::string &text) { + body->filter_search_fuzzy(text); + body->clamp_selection(); + }; + + search_bar->onTextSubmitCallback = [this](const std::string &text) { + BodyItem *selected_item = body->get_selected(); + printf("Selected item: %s\n", selected_item->title.c_str()); + if(!selected_item) + return; + video_url = selected_item->url; + current_page = Page::VIDEO_CONTENT; + page_view_stack.push(current_page); + }; + + sf::Vector2f body_pos; + sf::Vector2f body_size; + bool resized = true; + + while (window.isOpen() && current_page == Page::SEARCH_RESULT) { + sf::Event event; + while (window.pollEvent(event)) { + if (event.type == sf::Event::Closed) { + window.close(); + current_page = Page::EXIT; + } else if(event.type == sf::Event::Resized) { + window_size.x = event.size.width; + window_size.y = event.size.height; + sf::FloatRect visible_area(0, 0, window_size.x, window_size.y); + window.setView(sf::View(visible_area)); + resized = true; + } else if(event.type == sf::Event::KeyPressed) { + if(event.key.code == sf::Keyboard::Up) { + body->select_previous_item(); + } else if(event.key.code == sf::Keyboard::Down) { + body->select_next_item(); + } else if(event.key.code == sf::Keyboard::Escape) { + current_page = Page::SEARCH_SUGGESTION; + body->clear_items(); + body->reset_selected(); + search_bar->clear(); + } + } else if(event.type == sf::Event::TextEntered) { + search_bar->onTextEntered(event.text.unicode); + } + } + + if(resized) { + search_bar->onWindowResize(window_size); + + float body_padding_horizontal = 50.0f; + float body_padding_vertical = 50.0f; + float body_width = window_size.x - body_padding_horizontal * 2.0f; + if(body_width < 400) { + body_width = window_size.x; + body_padding_horizontal = 0.0f; + } + + float search_bottom = search_bar->getBottom(); + body_pos = sf::Vector2f(body_padding_horizontal, search_bottom + body_padding_vertical); + body_size = sf::Vector2f(body_width, window_size.y); + } + + search_bar->update(); + + window.clear(back_color); + body->draw(window, body_pos, body_size); + search_bar->draw(window); + window.display(); + } + } + + void Program::video_content_page() { + search_bar->onTextUpdateCallback = nullptr; + search_bar->onTextSubmitCallback = nullptr; + + std::unique_ptr<VideoPlayer> video_player; + try { + printf("Play video: %s\n", video_url.c_str()); + video_player = std::make_unique<VideoPlayer>(window_size.x, window_size.y, video_url.c_str()); + } catch(VideoInitializationException &e) { + fprintf(stderr, "Failed to create video player!. TODO: Show this to the user"); + } + + bool resized = false; + sf::Clock resize_timer; + + while (window.isOpen() && current_page == Page::VIDEO_CONTENT) { + sf::Event event; + while (window.pollEvent(event)) { + if (event.type == sf::Event::Closed) { + window.close(); + current_page = Page::EXIT; + } else if(event.type == sf::Event::Resized) { + window_size.x = event.size.width; + window_size.y = event.size.height; + sf::FloatRect visible_area(0, 0, window_size.x, window_size.y); + window.setView(sf::View(visible_area)); + resized = true; + resize_timer.restart(); + } else if(event.type == sf::Event::KeyPressed) { + if(event.key.code == sf::Keyboard::Escape) { + current_page = Page::SEARCH_SUGGESTION; + body->clear_items(); + body->reset_selected(); + search_bar->clear(); + } + } + } + + if(resized && resize_timer.getElapsedTime().asMilliseconds() >= 300) { + resized = false; + if(video_player) { + if(!video_player->resize(sf::Vector2i(window_size.x, window_size.y))) + video_player.release(); + } + } + + window.clear(); + if(video_player) + video_player->draw(window); + window.display(); + } + } +}
\ No newline at end of file |