From e308d77b06405b91885cf6f97c0dc2a1b70679ef Mon Sep 17 00:00:00 2001 From: dec05eba Date: Tue, 11 May 2021 14:24:52 +0200 Subject: Improve file saving gui --- README.md | 5 +- TODO | 3 +- include/Entry.hpp | 3 + include/QuickMedia.hpp | 2 + include/RoundedRectangle.hpp | 5 +- include/SearchBar.hpp | 3 + include/Text.hpp | 2 + include/gui/Button.hpp | 40 ++++++ plugins/FileManager.hpp | 16 +-- src/Body.cpp | 2 +- src/DownloadUtils.cpp | 1 + src/Entry.cpp | 22 +++- src/QuickMedia.cpp | 298 +++++++++++++++++++++++++++++++++++-------- src/RoundedRectangle.cpp | 13 +- src/SearchBar.cpp | 39 ++++-- src/Text.cpp | 7 +- src/gui/Button.cpp | 76 +++++++++++ src/plugins/FileManager.cpp | 16 +-- src/plugins/Mangadex.cpp | 2 +- 19 files changed, 442 insertions(+), 113 deletions(-) create mode 100644 include/gui/Button.hpp create mode 100644 src/gui/Button.cpp diff --git a/README.md b/README.md index 72fb795..56bd281 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Press `P` to preview the 4chan image of the selected row in full screen view, pr Press `I` to switch between single image and scroll image view mode when reading manga.\ Press `F` to fit image to window size when reading manga. Press `F` again to show original image size.\ Press `Middle mouse button` to "autoscroll" in scrolling image view mode.\ -Press `Tab` to switch between username/password field in login panel.\ +Press `Tab` to switch between username/password field in login panel or to switch between search and filename in download menu.\ Press `Ctrl + C` to copy the url of the currently playing video to the clipboard (with timestamp).\ Press `Ctrl + V` to paste the content of your clipboard into the search bar.\ Press `Enter` to view image/video attached to matrix message, or to view the url in the message in quickmedia (youtube) or in the browser.\ @@ -60,7 +60,8 @@ Press `Ctrl + D` to remove the file that was previously selected with `U` in a 4 Press `Ctrl + I` to reverse image search the selected image on 4chan or matrix.\ Press `Ctrl+Alt+Arrow up` / `Ctrl+Alt+Arrow down` or `Ctrl+Alt+K` / `Ctrl+Alt+J` to view the room above/below the selected room in matrix.\ Press `Ctrl + S` to save the displaying image/video/audio (does currently not work for manga pages).\ -Press `Ctrl + Enter` to submit text, ignoring the selected item (when saving a file or selecting a server for matrix room directory). +Press `Ctrl + Enter` to submit text, ignoring the selected item (when saving a file or selecting a server for matrix room directory).\ +Press `Ctrl + Enter` to save the file to the selected directory with the selected name, when downloading a file. In matrix you can select a message with enter to open the url in the message (or if there are multiple urls then a menu will appear for selecting which to open). ## Matrix commands diff --git a/TODO b/TODO index 5bf0c1f..90369ec 100644 --- a/TODO +++ b/TODO @@ -129,5 +129,4 @@ Add client side 4chan max comment chars limit. Use a directory icon and a file icon for non-media files in the file manager. Dynamically fetch 4chan api key, if it ever changes in the future. Same for youtube. Set curl download limits everywhere (when saving to file, downloading to json, etc...). -In the downloader if we already have the url in thumbnail/video cache, then copy it to the destination instead of redownloading it. This would also fix downloading images when viewing a manga page. -Improve file saving ui. We want it to display the remote name of the file so we dont have to manually type it. \ No newline at end of file +In the downloader if we already have the url in thumbnail/video cache, then copy it to the destination instead of redownloading it. This would also fix downloading images when viewing a manga page. \ No newline at end of file diff --git a/include/Entry.hpp b/include/Entry.hpp index 02d0a59..581dc14 100644 --- a/include/Entry.hpp +++ b/include/Entry.hpp @@ -22,6 +22,7 @@ namespace QuickMedia { void process_event(sf::Event &event); void draw(sf::RenderWindow &window); + void set_single_line(bool single_line); void set_editable(bool editable); void set_text(std::string text); void set_position(const sf::Vector2f &pos); @@ -29,7 +30,9 @@ namespace QuickMedia { void move_caret_to_end(); void append_text(std::string str); + bool is_editable() const; float get_height(); + const sf::String& get_text() const; OnEntrySubmit on_submit_callback; bool draw_background; diff --git a/include/QuickMedia.hpp b/include/QuickMedia.hpp index 01baf76..0ef5273 100644 --- a/include/QuickMedia.hpp +++ b/include/QuickMedia.hpp @@ -120,6 +120,8 @@ namespace QuickMedia { bool chat_page(MatrixChatPage *matrix_chat_page, RoomData *current_room, std::vector &room_tabs, int room_selected_tab); void after_matrix_login_page(); void download_page(const char *url, bool download_use_youtube_dl); + // Returns the full path where the file should be saved, or an empty string if the operation was cancelled + std::string file_save_page(const std::string &filename); enum class LoadImageResult { OK, diff --git a/include/RoundedRectangle.hpp b/include/RoundedRectangle.hpp index f72a083..96e57fd 100644 --- a/include/RoundedRectangle.hpp +++ b/include/RoundedRectangle.hpp @@ -4,7 +4,7 @@ #include namespace sf { - class RenderWindow; + class RenderTarget; class Shader; } @@ -14,9 +14,10 @@ namespace QuickMedia { RoundedRectangle(sf::Vector2f size, float radius, sf::Color color, sf::Shader *rounded_rectangle_shader); void set_position(sf::Vector2f pos); void set_size(sf::Vector2f size); + void set_color(sf::Color color); sf::Vector2f get_position() const; sf::Vector2f get_size() const; - void draw(sf::RenderWindow &window); + void draw(sf::RenderTarget &target); private: float radius; sf::Vector2f pos; diff --git a/include/SearchBar.hpp b/include/SearchBar.hpp index cad897f..4fc19e9 100644 --- a/include/SearchBar.hpp +++ b/include/SearchBar.hpp @@ -33,7 +33,9 @@ namespace QuickMedia { void set_to_autocomplete(); void set_autocomplete_text(const std::string &text); void set_position(sf::Vector2f pos); + void set_editable(bool editable); + bool is_editable() const; float getBottom() const; float getBottomWithoutShadow() const; @@ -74,5 +76,6 @@ namespace QuickMedia { sf::Vector2f pos; sf::Clock time_since_search_update; sf::Vector2f prev_size; + bool editable = true; }; } \ No newline at end of file diff --git a/include/Text.hpp b/include/Text.hpp index 53105f5..cef6dd5 100644 --- a/include/Text.hpp +++ b/include/Text.hpp @@ -104,6 +104,8 @@ namespace QuickMedia bool draw(sf::RenderTarget &target); void updateGeometry(bool update_even_if_not_dirty = false); + + bool single_line_edit = false; private: enum class CaretMoveDirection : u8 { diff --git a/include/gui/Button.hpp b/include/gui/Button.hpp new file mode 100644 index 0000000..ba6997b --- /dev/null +++ b/include/gui/Button.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include "../RoundedRectangle.hpp" +#include +#include + +namespace sf { + class Event; + class Font; + class RenderTarget; + class Shader; +} + +namespace QuickMedia { + enum ButtonEvent { + BUTTON_EVENT_NONE = 0, + BUTTON_EVENT_CLICKED = 1 + }; + + class Button { + public: + Button(const std::string &label, sf::Font *font, unsigned int character_size, float width, sf::Shader *rounded_rectangle_shader, float scale = 1.0f); + + ButtonEvent on_event(sf::Event &event); + void draw(sf::RenderTarget &target); + + void set_background_color(sf::Color color); + void set_position(sf::Vector2f pos); + + sf::Vector2f get_position() const; + float get_width() const; + float get_height() const; + private: + sf::Text label; + RoundedRectangle background; + sf::Color background_color; + float scale; + bool clicked_inside = false; + }; +} \ No newline at end of file diff --git a/plugins/FileManager.hpp b/plugins/FileManager.hpp index 20ed49c..e452c40 100644 --- a/plugins/FileManager.hpp +++ b/plugins/FileManager.hpp @@ -18,27 +18,19 @@ namespace QuickMedia { class FileManagerPage : public Page { public: - FileManagerPage(Program *program, FileManagerMimeType mime_type = FILE_MANAGER_MIME_TYPE_ALL, FileSelectionHandler selection_handler = nullptr, bool allow_empty_match_submit = false, const std::string &title_prefix = "") : - Page(program), current_dir("/"), mime_type(mime_type), selection_handler(selection_handler), allow_empty_match_submit(allow_empty_match_submit), title_prefix(title_prefix) - { - title = title_prefix + current_dir.string(); - } - const char* get_title() const override { return title.c_str(); } + FileManagerPage(Program *program, FileManagerMimeType mime_type = FILE_MANAGER_MIME_TYPE_ALL, FileSelectionHandler selection_handler = nullptr) : + Page(program), current_dir("/"), mime_type(mime_type), selection_handler(selection_handler) {} + const char* get_title() const override { return current_dir.c_str(); } PluginResult submit(const std::string &title, const std::string &url, std::vector &result_tabs) override; bool is_single_page() const override { return true; } - bool allow_submit_no_selection() const override { return allow_empty_match_submit; } - void on_navigate_to_page(Body*) override; bool set_current_directory(const std::string &path); + const std::filesystem::path& get_current_directory() const { return current_dir; } PluginResult get_files_in_directory(BodyItems &result_items); - - bool close = false; private: std::filesystem::path current_dir; FileManagerMimeType mime_type; FileSelectionHandler selection_handler; bool allow_empty_match_submit; - std::string title; - std::string title_prefix; }; } \ No newline at end of file diff --git a/src/Body.cpp b/src/Body.cpp index 9494e80..c186b9c 100644 --- a/src/Body.cpp +++ b/src/Body.cpp @@ -335,7 +335,6 @@ namespace QuickMedia { void Body::clear_cache() { clear_text_cache(); clear_thumbnails(); - malloc_trim(0); } void Body::clear_text_cache() { @@ -348,6 +347,7 @@ namespace QuickMedia { void Body::clear_thumbnails() { item_thumbnail_textures.clear(); + malloc_trim(0); } BodyItem* Body::get_selected() const { diff --git a/src/DownloadUtils.cpp b/src/DownloadUtils.cpp index bb60ad6..248fda0 100644 --- a/src/DownloadUtils.cpp +++ b/src/DownloadUtils.cpp @@ -1,6 +1,7 @@ #include "../include/DownloadUtils.hpp" #include "../include/Program.hpp" #include "../include/Storage.hpp" +#include "../include/NetUtils.hpp" #include "../external/cppcodec/base64_url.hpp" #include #include diff --git a/src/Entry.cpp b/src/Entry.cpp index 96a34f3..5e18340 100644 --- a/src/Entry.cpp +++ b/src/Entry.cpp @@ -7,7 +7,7 @@ #include const float background_margin_horizontal = std::floor(5.0f * QuickMedia::get_ui_scale()); -const float padding_vertical = std::floor(3.0f * QuickMedia::get_ui_scale()); +const float padding_vertical = std::floor(5.0f * QuickMedia::get_ui_scale()); const float background_margin_vertical = std::floor(0.0f * QuickMedia::get_ui_scale()); namespace QuickMedia { @@ -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), 7.0f, sf::Color(55, 60, 68), rounded_rectangle_shader), + background(sf::Vector2f(1.0f, 1.0f), 10.0f, sf::Color(55, 60, 68), rounded_rectangle_shader), placeholder(placeholder_text, *FontLoader::get_font(FontLoader::FontType::LATIN), std::floor(16 * get_ui_scale())), mouse_left_inside(false) { @@ -74,6 +74,10 @@ namespace QuickMedia { } } + void Entry::set_single_line(bool single_line) { + text.single_line_edit = single_line; + } + void Entry::set_editable(bool editable) { text.setEditable(editable); } @@ -93,8 +97,8 @@ namespace QuickMedia { void Entry::set_position(const sf::Vector2f &pos) { background.set_position(pos); - text.setPosition(pos + sf::Vector2f(background_margin_horizontal, background_margin_vertical - std::floor(3.0f * get_ui_scale()))); - placeholder.setPosition(pos + sf::Vector2f(background_margin_horizontal, background_margin_vertical + std::floor(3.0f * get_ui_scale()))); + text.setPosition(pos + sf::Vector2f(background_margin_horizontal, background_margin_vertical)); + placeholder.setPosition(pos + sf::Vector2f(background_margin_horizontal, background_margin_vertical)); } void Entry::set_max_width(float width) { @@ -102,8 +106,16 @@ namespace QuickMedia { text.setMaxWidth(this->width - background_margin_horizontal * 2.0f); } + bool Entry::is_editable() const { + return text.isEditable(); + } + float Entry::get_height() { text.updateGeometry(); - return std::floor(text.getHeight() + background_margin_vertical * 2.0f + padding_vertical *2.0f); + return std::floor(text.getHeight() + background_margin_vertical * 2.0f + padding_vertical * 2.0f); + } + + const sf::String& Entry::get_text() const { + return text.getString(); } } \ No newline at end of file diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index b4fa915..13b9369 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -29,6 +29,7 @@ #include "../include/ResourceLoader.hpp" #include "../include/Utils.hpp" #include "../include/Tabs.hpp" +#include "../include/gui/Button.hpp" #include "../external/hash-library/sha256.h" #include @@ -599,7 +600,7 @@ namespace QuickMedia { window.create(x11_window); - if(program_path.back() != '/') + if(!program_path.empty() && program_path.back() != '/') program_path += '/'; resources_root = "/usr/share/quickmedia/"; @@ -1531,6 +1532,7 @@ namespace QuickMedia { if(tabs[selected_tab].page->is_single_page()) { if(tabs[selected_tab].search_bar) tabs[selected_tab].search_bar->clear(); if(new_tabs.size() == 1 && !new_tabs[0].page) { + tabs[selected_tab].body->clear_thumbnails(); tabs[selected_tab].body = std::move(new_tabs[0].body); tabs[selected_tab].page->submit_body_item = prev_selected_item; return; @@ -1854,6 +1856,7 @@ namespace QuickMedia { if(associated_data.fetch_status == FetchStatus::LOADING && associated_data.fetch_type == FetchType::SEARCH && associated_data.fetch_future.ready()) { if(!associated_data.search_text_updated) { FetchResult fetch_result = associated_data.fetch_future.get(); + tabs[i].body->clear_thumbnails(); tabs[i].body->items = std::move(fetch_result.body_items); tabs[i].body->select_first_item(); associated_data.fetched_page = 0; @@ -2270,7 +2273,7 @@ namespace QuickMedia { } else if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::C && event.key.control) { save_video_url_to_clipboard(); } else if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::S && event.key.control) { - download_async_gui(original_video_url, true, force_no_video); + download_async_gui(original_video_url, true, no_video); } } handle_window_close(); @@ -2287,7 +2290,7 @@ namespace QuickMedia { } else if(pressed_keysym == XK_f && pressing_ctrl) { window_set_fullscreen(disp, window.getSystemHandle(), WindowFullscreenState::TOGGLE); } else if(pressed_keysym == XK_s && pressing_ctrl) { - download_async_gui(original_video_url, true, force_no_video); + download_async_gui(original_video_url, true, no_video); } else if(pressed_keysym == XK_r && pressing_ctrl) { if(!cursor_visible) window.setMouseCursorVisible(true); @@ -2595,7 +2598,7 @@ namespace QuickMedia { }; } else if(website_url && website_url[0] != '\0') { std::string website_url_str = website_url; - if(website_url_str.back() != '/') + if(!website_url_str.empty() && website_url_str.back() != '/') website_url_str.push_back('/'); extra_args = { CommandArg { "-H", "referer: " + std::move(website_url_str) }, @@ -4733,7 +4736,7 @@ namespace QuickMedia { if(selected_item_message) { MessageType message_type = selected_item_message->type; if(!selected->url.empty() && (message_type == MessageType::VIDEO || message_type == MessageType::IMAGE || message_type == MessageType::AUDIO)) { - download_async_gui(selected->url, false, force_no_video); + download_async_gui(selected->url, false, no_video); return true; } } @@ -5919,60 +5922,57 @@ namespace QuickMedia { bool no_video; }; - static const char* get_filename(const char *path) { - const char *p = (const char*)memrchr(path, '/', strlen(path)); - return p ? p + 1 : path; + static int accumulate_string(char *data, int size, void *userdata) { + std::string *str = (std::string*)userdata; + if(str->size() + size > 1024 * 1024 * 100) // 100mb sane limit, TODO: make configurable + return 1; + str->append(data, size); + return 0; } - class ConfirmationPage : public Page { - public: - ConfirmationPage(Program *program, FileManagerPage *file_manager_page, bool *file_overwrite, const std::string &title) : Page(program), file_manager_page(file_manager_page), file_overwrite(file_overwrite), title(title) {} - const char* get_title() const override { return title.c_str(); } - PluginResult submit(const std::string &title, const std::string&, std::vector&) override { - if(title == "Yes") { - *file_overwrite = true; - file_manager_page->close = true; - } else { - *file_overwrite = false; + void Program::download_page(const char *url, bool download_use_youtube_dl) { + window.setTitle("QuickMedia - Select where you want to save " + std::string(url)); + + std::string filename; + TaskResult task_result = run_task_with_loading_screen([this, url, &filename]{ + std::string json_str; + std::vector args = { "youtube-dl", "--skip-download", "--print-json", "--no-warnings" }; + if(no_video) + args.push_back("-x"); + args.insert(args.end(), { "--", url, nullptr }); + if(exec_program(args.data(), accumulate_string, &json_str) != 0) + return false; + + Json::Value result; + Json::CharReaderBuilder json_builder; + std::unique_ptr json_reader(json_builder.newCharReader()); + std::string json_errors; + if(!json_reader->parse(json_str.data(), json_str.data() + json_str.size(), &result, &json_errors)) { + fprintf(stderr, "Failed to json response, error: %s\n", json_errors.c_str()); + return false; } - program->set_go_to_previous_page(); - return PluginResult::OK; - } - static void add_items(BodyItems &items) { - items.push_back(BodyItem::create("No")); - items.push_back(BodyItem::create("Yes")); - } - private: - FileManagerPage *file_manager_page; - bool *file_overwrite; - std::string title; - }; + const Json::Value &title_json = result["title"]; + const Json::Value &ext_json = result["ext"]; + if(title_json.isString()) + filename = title_json.asString(); - void Program::download_page(const char *url, bool download_use_youtube_dl) { - bool file_overwrite = true; - FileSelectionHandler overwrite_confirm_handler = [this, &file_overwrite](FileManagerPage *file_manager_page, const std::filesystem::path &path) { - file_overwrite = true; - std::vector tabs; - if(std::filesystem::exists(path)) { - auto body = create_body(); - ConfirmationPage::add_items(body->items); - tabs.push_back(Tab{ std::move(body), std::make_unique(this, file_manager_page, &file_overwrite, "Are you sure you want to overwrite " + path.string() + "?"), nullptr }); + if(ext_json.isString()) { + if(ext_json.asCString()[0] != '.' && (filename.empty() || filename.back() != '.')) + filename += "."; + filename += ext_json.asString(); } - return tabs; - }; - auto file_manager_page = std::make_unique(this, FILE_MANAGER_MIME_TYPE_ALL, std::move(overwrite_confirm_handler), true, "Where do you want to save the file? Current directory: "); - file_manager_page->set_current_directory(get_home_dir().data); - auto file_manager_body = create_body(); - file_manager_page->get_files_in_directory(file_manager_body->items); - std::vector file_manager_tabs; - file_manager_tabs.push_back(Tab{std::move(file_manager_body), std::move(file_manager_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + return !filename.empty(); + }); - selected_files.clear(); - page_loop(file_manager_tabs); + if(task_result == TaskResult::CANCEL) { + exit_code = 1; + return; + } - if(!window.isOpen() || selected_files.empty() || !file_overwrite) { + std::string output_filepath = file_save_page(filename); + if(!window.isOpen() || output_filepath.empty()) { exit_code = 1; return; } @@ -5998,7 +5998,6 @@ namespace QuickMedia { } window.setPosition(sf::Vector2i(focused_monitor_center.x - window_size.x * 0.5f, focused_monitor_center.y - window_size.y * 0.5f)); - std::string output_filepath = selected_files[0]; std::string output_filepath_s = output_filepath; char *output_dir = dirname(output_filepath_s.data()); if(create_directory_recursive(output_dir) != 0) { @@ -6022,7 +6021,7 @@ namespace QuickMedia { sf::Text progress_text("0kb/Unknown", *FontLoader::get_font(FontLoader::FontType::LATIN), std::floor(20.0f * get_ui_scale())); sf::Text status_text("Downloading", *FontLoader::get_font(FontLoader::FontType::LATIN), std::floor(20.0f * get_ui_scale())); - sf::Text filename_text(get_filename(output_filepath.c_str()), *FontLoader::get_font(FontLoader::FontType::LATIN), std::floor(14.0f * get_ui_scale())); + sf::Text filename_text(filename.c_str(), *FontLoader::get_font(FontLoader::FontType::LATIN), std::floor(14.0f * get_ui_scale())); filename_text.setFillColor(sf::Color(179, 179, 179)); sf::Text download_speed_text("0 bytes/s", *FontLoader::get_font(FontLoader::FontType::LATIN), std::floor(14.0f * get_ui_scale())); download_speed_text.setFillColor(sf::Color(179, 179, 179)); @@ -6032,7 +6031,7 @@ namespace QuickMedia { std::unique_ptr downloader; if(download_use_youtube_dl) - downloader = std::make_unique(url, output_filepath, force_no_video); + downloader = std::make_unique(url, output_filepath, no_video); else downloader = std::make_unique(url, output_filepath); @@ -6139,4 +6138,197 @@ namespace QuickMedia { } exit(exit_code); } + + class ConfirmationPage : public Page { + public: + ConfirmationPage(Program *program, bool *file_overwrite, const std::string &title) : Page(program), file_overwrite(file_overwrite), title(title) {} + const char* get_title() const override { return title.c_str(); } + PluginResult submit(const std::string &title, const std::string&, std::vector&) override { + if(title == "Yes") + *file_overwrite = true; + else + *file_overwrite = false; + + program->set_go_to_previous_page(); + return PluginResult::OK; + } + + static void add_items(BodyItems &items) { + items.push_back(BodyItem::create("No")); + items.push_back(BodyItem::create("Yes")); + } + private: + bool *file_overwrite; + std::string title; + }; + + std::string Program::file_save_page(const std::string &filename) { + sf::Vector2f body_pos; + sf::Vector2f body_size; + bool redraw = true; + sf::Event event; + + auto file_manager_page = std::make_unique(this); + std::string home_dir = get_home_dir().data; + file_manager_page->set_current_directory(home_dir); + auto file_manager_body = create_body(); + file_manager_page->get_files_in_directory(file_manager_body->items); + 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(home_dir); + + search_bar->onTextUpdateCallback = [&file_manager_body](const std::string &text) { + file_manager_body->filter_search_fuzzy(text); + file_manager_body->select_first_item(); + }; + + search_bar->onTextSubmitCallback = [this, &search_bar, &file_manager_body, &file_manager_page, &ui_tabs, tab_path_index](const std::string&) { + if(sf::Keyboard::isKeyPressed(sf::Keyboard::LControl) || sf::Keyboard::isKeyPressed(sf::Keyboard::RControl)) + return; + + BodyItem *selected = file_manager_body->get_selected(); + if(!selected) + return; + + std::vector new_tabs; + TaskResult task_result = run_task_with_loading_screen([selected, &file_manager_page, &new_tabs]() { + return file_manager_page->submit(selected->get_title(), selected->url, new_tabs) == PluginResult::OK; + }); + + if(task_result == TaskResult::TRUE) { + if(!new_tabs.empty()) { + file_manager_body->clear_thumbnails(); + file_manager_body->items = std::move(new_tabs[0].body->items); + } + } else if(task_result == TaskResult::FALSE) { + show_notification("QuickMedia", "Failed to change directory", Urgency::CRITICAL); + } + + search_bar->clear(); + ui_tabs.set_text(tab_path_index, file_manager_page->get_current_directory().string()); + }; + + const float bottom_panel_padding = 10.0f; + const float bottom_panel_spacing = 10.0f; + + Button cancel_button("Cancel", FontLoader::get_font(FontLoader::FontType::LATIN), 16, 100.0f, &rounded_rectangle_shader, get_ui_scale()); + cancel_button.set_background_color(sf::Color(104, 2, 2)); + + Button save_button("Save", FontLoader::get_font(FontLoader::FontType::LATIN), 16, 100.0f, &rounded_rectangle_shader, get_ui_scale()); + save_button.set_background_color(sf::Color(35, 35, 236)); + + sf::Text file_name_label("File name:", *FontLoader::get_font(FontLoader::FontType::LATIN), std::floor(16.0f * get_ui_scale())); + + Entry file_name_entry("", &rounded_rectangle_shader); + file_name_entry.set_text(filename); + file_name_entry.set_single_line(true); + file_name_entry.set_editable(false); + + sf::RectangleShape bottom_panel_background; + bottom_panel_background.setFillColor(sf::Color(33, 37, 44)); + + auto save_file = [this, &file_name_entry, &file_manager_page]() -> std::string { + auto u8 = file_name_entry.get_text().toUtf8(); + std::string *filename = (std::string*)&u8; + + Path filename_full_path = file_manager_page->get_current_directory().string(); + filename_full_path.join(*filename); + + if(filename->empty()) { + show_notification("QuickMedia", "The file name can't be empty", Urgency::CRITICAL); + } else if(*filename == "." || *filename == ".." || filename->find('/') != std::string::npos) { + show_notification("QuickMedia", "Invalid file name. File can't be ., .. or contain /", Urgency::CRITICAL); + } else if(filename->size() >= 255 || filename_full_path.data.size() >= 4096) { + show_notification("QuickMedia", "The file name has to be less than 255 characters and the full path has to be less than 4096 characters", Urgency::CRITICAL); + } else { + if(std::filesystem::exists(filename_full_path.data)) { + bool overwrite = false; + std::vector tabs; + auto body = create_body(); + ConfirmationPage::add_items(body->items); + tabs.push_back(Tab{ std::move(body), std::make_unique(this, &overwrite, "Are you sure you want to overwrite " + filename_full_path.data + "?"), nullptr }); + page_loop(tabs); + if(overwrite) + return std::move(filename_full_path.data); + } else { + return std::move(filename_full_path.data); + } + } + + return ""; + }; + + while (window.isOpen()) { + while (window.pollEvent(event)) { + if(file_manager_body->on_event(window, event, !file_name_entry.is_editable())) + idle_active_handler(); + else + event_idle_handler(event); + + search_bar->on_event(event); + if(cancel_button.on_event(event) & BUTTON_EVENT_CLICKED) + return ""; + if(save_button.on_event(event) & BUTTON_EVENT_CLICKED) { + std::string save_path = save_file(); + if(!save_path.empty()) + return save_path; + } + file_name_entry.process_event(event); + + 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)); + redraw = true; + idle_active_handler(); + } else if(event.type == sf::Event::GainedFocus) { + redraw = true; + } else if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Tab) { + file_name_entry.set_editable(!file_name_entry.is_editable()); + search_bar->set_editable(!file_name_entry.is_editable()); + } else if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Enter && event.key.control) { + std::string save_path = save_file(); + if(!save_path.empty()) + return save_path; + } + } + + update_idle_state(); + handle_window_close(); + search_bar->update(); + + if(redraw) { + redraw = false; + get_body_dimensions(window_size, search_bar.get(), body_pos, body_size); + body_pos.y += std::floor(10.0f * get_ui_scale()) + Tabs::get_height(); + body_size.y -= std::floor(10.0f * get_ui_scale()) + Tabs::get_height(); + save_button.set_position(window_size - sf::Vector2f(save_button.get_width(), save_button.get_height()) - sf::Vector2f(bottom_panel_padding, bottom_panel_padding)); + cancel_button.set_position(save_button.get_position() - sf::Vector2f(cancel_button.get_width() + bottom_panel_spacing, 0.0f)); + file_name_label.setPosition(sf::Vector2f(bottom_panel_spacing, std::floor(window_size.y - bottom_panel_padding - file_name_entry.get_height() * 0.5f - file_name_label.getLocalBounds().height * 0.5f - 5.0f * get_ui_scale()))); + file_name_entry.set_position(sf::Vector2f(file_name_label.getPosition().x + file_name_label.getLocalBounds().width + bottom_panel_spacing, window_size.y - file_name_entry.get_height() - bottom_panel_padding)); + file_name_entry.set_max_width(std::floor(cancel_button.get_position().x - bottom_panel_spacing - file_name_label.getLocalBounds().width - bottom_panel_spacing - bottom_panel_spacing)); + bottom_panel_background.setPosition(0.0f, window_size.y - std::floor(bottom_panel_padding * 2.0f + file_name_entry.get_height())); + bottom_panel_background.setSize(sf::Vector2f(window_size.x, std::floor(bottom_panel_padding * 2.0f + file_name_entry.get_height()))); + } + + window.clear(back_color); + + ui_tabs.draw(window, sf::Vector2f(0.0f, search_bar->getBottomWithoutShadow()), window_size.x); + search_bar->draw(window, window_size, true); + + file_manager_body->draw(window, body_pos, body_size - sf::Vector2f(0.0f, bottom_panel_background.getSize().y)); + + window.draw(bottom_panel_background); + window.draw(file_name_label); + cancel_button.draw(window); + save_button.draw(window); + file_name_entry.draw(window); + + window.display(); + } + + return ""; + } } diff --git a/src/RoundedRectangle.cpp b/src/RoundedRectangle.cpp index 6b956e8..4b06ea8 100644 --- a/src/RoundedRectangle.cpp +++ b/src/RoundedRectangle.cpp @@ -1,6 +1,6 @@ #include "../include/RoundedRectangle.hpp" #include -#include +#include #include namespace QuickMedia { @@ -30,6 +30,13 @@ namespace QuickMedia { set_position(pos); } + void RoundedRectangle::set_color(sf::Color color) { + vertices[0].color = color; + vertices[1].color = color; + vertices[2].color = color; + vertices[3].color = color; + } + sf::Vector2f RoundedRectangle::get_position() const { return pos; } @@ -38,9 +45,9 @@ namespace QuickMedia { return size; } - void RoundedRectangle::draw(sf::RenderWindow &window) { + void RoundedRectangle::draw(sf::RenderTarget &target) { rounded_rectangle_shader->setUniform("resolution", size); rounded_rectangle_shader->setUniform("radius", radius); - window.draw(vertices, 4, sf::Quads, rounded_rectangle_shader); + target.draw(vertices, 4, sf::Quads, rounded_rectangle_shader); } } \ No newline at end of file diff --git a/src/SearchBar.cpp b/src/SearchBar.cpp index 8362864..7d7f72d 100644 --- a/src/SearchBar.cpp +++ b/src/SearchBar.cpp @@ -82,7 +82,7 @@ namespace QuickMedia { caret.setPosition(text.findCharacterPos(text.getString().getSize()) + sf::Vector2f(0.0f, 2.0f)); } - if(caret_visible) + if(caret_visible && is_editable()) window.draw(caret); if(draw_logo) @@ -90,6 +90,22 @@ namespace QuickMedia { } void SearchBar::on_event(sf::Event &event) { + if(is_touch_enabled() && event.type == sf::Event::MouseButtonPressed && event.mouseButton.button == sf::Mouse::Left) { + sf::FloatRect box(background.get_position(), background.get_size()); + if(box.contains(event.mouseButton.x, event.mouseButton.y)) + mouse_left_inside = true; + else + mouse_left_inside = false; + } else if(is_touch_enabled() && event.type == sf::Event::MouseButtonReleased && event.mouseButton.button == sf::Mouse::Left) { + sf::FloatRect box(background.get_position(), background.get_size()); + if(mouse_left_inside && box.contains(event.mouseButton.x, event.mouseButton.y)) + show_virtual_keyboard(); + mouse_left_inside = false; + } + + if(!editable) + 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) @@ -110,19 +126,6 @@ namespace QuickMedia { onTextEntered(event.text.unicode); else if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Backspace) onTextEntered(8); - - if(is_touch_enabled() && event.type == sf::Event::MouseButtonPressed && event.mouseButton.button == sf::Mouse::Left) { - sf::FloatRect box(background.get_position(), background.get_size()); - if(box.contains(event.mouseButton.x, event.mouseButton.y)) - mouse_left_inside = true; - else - mouse_left_inside = false; - } else if(is_touch_enabled() && event.type == sf::Event::MouseButtonReleased && event.mouseButton.button == sf::Mouse::Left) { - sf::FloatRect box(background.get_position(), background.get_size()); - if(mouse_left_inside && box.contains(event.mouseButton.x, event.mouseButton.y)) - show_virtual_keyboard(); - mouse_left_inside = false; - } } void SearchBar::update() { @@ -303,6 +306,14 @@ namespace QuickMedia { } } + void SearchBar::set_editable(bool editable) { + this->editable = editable; + } + + bool SearchBar::is_editable() const { + return editable; + } + void SearchBar::clear_autocomplete_if_text_not_substring() { const sf::String &text_str = text.getString(); const sf::String &autocomplete_str = autocomplete_text.getString(); diff --git a/src/Text.cpp b/src/Text.cpp index 0431c4e..2940520 100644 --- a/src/Text.cpp +++ b/src/Text.cpp @@ -60,10 +60,11 @@ namespace QuickMedia { //if(str != this->str) //{ + size_t prev_str_size = this->str.getSize(); this->str = std::move(str); dirty = true; dirtyText = true; - if((int)this->str.getSize() < caretIndex) + if((int)this->str.getSize() < caretIndex || prev_str_size == 0) { caretIndex = this->str.getSize(); dirtyCaret = true; @@ -821,7 +822,7 @@ namespace QuickMedia } else if(event.key.code == sf::Keyboard::Enter) { - if(event.key.shift) + if(event.key.shift && !single_line_edit) { if(caretAtEnd) str += '\n'; @@ -849,7 +850,7 @@ namespace QuickMedia { stringToAdd = sf::Clipboard::getString(); } - else if(event.text.unicode >= 32 || event.text.unicode == 9) // 9 == tab + else if(event.text.unicode >= 32 || (event.text.unicode == 9 && !single_line_edit)) // 9 == tab stringToAdd = event.text.unicode; else return; diff --git a/src/gui/Button.cpp b/src/gui/Button.cpp new file mode 100644 index 0000000..ebbb8bb --- /dev/null +++ b/src/gui/Button.cpp @@ -0,0 +1,76 @@ +#include "../../include/gui/Button.hpp" +#include +#include +#include + +namespace QuickMedia { + static const float PADDING_Y = 10.0f; + + Button::Button(const std::string &label, sf::Font *font, unsigned int character_size, float width, sf::Shader *rounded_rectangle_shader, float scale) : + label(label, *font, character_size * scale), + background(sf::Vector2f(1.0f, 1.0f), 10.0f, sf::Color(33, 37, 44), rounded_rectangle_shader), + scale(scale) + { + background.set_size(sf::Vector2f(std::floor(width * scale), get_height())); + set_position(sf::Vector2f(0.0f, 0.0f)); + } + + ButtonEvent Button::on_event(sf::Event &event) { + ButtonEvent performed_event = BUTTON_EVENT_NONE; + if(event.type == sf::Event::MouseMoved) { + if(sf::FloatRect(background.get_position(), background.get_size()).contains(event.mouseMove.x, event.mouseMove.y)) { + const int inc = 20; + background.set_color(sf::Color( + std::min(255, (int)background_color.r + inc), + std::min(255, (int)background_color.g + inc), + std::min(255, (int)background_color.b + inc))); + } else { + background.set_color(background_color); + } + } else if(event.type == sf::Event::MouseButtonPressed) { + if(event.mouseButton.button == sf::Mouse::Left && sf::FloatRect(background.get_position(), background.get_size()).contains(event.mouseButton.x, event.mouseButton.y)) { + clicked_inside = true; + } else { + clicked_inside = false; + } + } else if(event.type == sf::Event::MouseButtonReleased) { + if(clicked_inside && event.mouseButton.button == sf::Mouse::Left && sf::FloatRect(background.get_position(), background.get_size()).contains(event.mouseButton.x, event.mouseButton.y)) { + performed_event = BUTTON_EVENT_CLICKED; + } + clicked_inside = false; + } + return performed_event; + } + + void Button::draw(sf::RenderTarget &target) { + background.draw(target); + target.draw(label); + } + + void Button::set_background_color(sf::Color color) { + background_color = color; + background.set_color(background_color); + } + + void Button::set_position(sf::Vector2f pos) { + background.set_position(pos); + + const auto label_bounds = label.getLocalBounds(); + sf::Vector2f label_pos(pos + background.get_size() * 0.5f - sf::Vector2f(label_bounds.width * 0.5f, label_bounds.height * 0.5f) - sf::Vector2f(0.0f, 5.0f * scale)); + label_pos.x = std::floor(label_pos.x); + label_pos.y = std::floor(label_pos.y); + label.setPosition(label_pos); + } + + sf::Vector2f Button::get_position() const { + return background.get_position(); + } + + float Button::get_width() const { + return background.get_size().x; + } + + float Button::get_height() const { + return std::floor((PADDING_Y * 2.0f) * scale + label.getLocalBounds().height); + } +} \ No newline at end of file diff --git a/src/plugins/FileManager.cpp b/src/plugins/FileManager.cpp index e1f3b04..6ee7e71 100644 --- a/src/plugins/FileManager.cpp +++ b/src/plugins/FileManager.cpp @@ -39,18 +39,10 @@ namespace QuickMedia { return PluginResult::OK; } - if(!std::filesystem::is_directory(new_path)) { - if(allow_empty_match_submit) { - program->select_file(new_path); - if(selection_handler) - result_tabs = selection_handler(this, new_path); - return PluginResult::OK; - } + if(!std::filesystem::is_directory(new_path)) return PluginResult::ERR; - } current_dir = std::move(new_path); - this->title = title_prefix + current_dir.string(); BodyItems result_items; PluginResult result = get_files_in_directory(result_items); @@ -63,16 +55,10 @@ namespace QuickMedia { return PluginResult::OK; } - void FileManagerPage::on_navigate_to_page(Body*) { - if(close) - program->set_go_to_previous_page(); - } - bool FileManagerPage::set_current_directory(const std::string &path) { if(!std::filesystem::is_directory(path)) return false; current_dir = path; - title = title_prefix + current_dir.string(); return true; } diff --git a/src/plugins/Mangadex.cpp b/src/plugins/Mangadex.cpp index f69484b..0b50366 100644 --- a/src/plugins/Mangadex.cpp +++ b/src/plugins/Mangadex.cpp @@ -216,7 +216,7 @@ namespace QuickMedia { return PluginResult::ERR; std::string base_url = base_url_json.asString(); - if(base_url.back() != '/') + if(!base_url.empty() && base_url.back() != '/') base_url += '/'; auto image_urls = static_cast(submit_body_item->extra.get())->image_urls; -- cgit v1.2.3