#include "../include/SearchBar.hpp" #include "../include/Theme.hpp" #include "../include/Scale.hpp" #include "../include/ResourceLoader.hpp" #include "../include/Config.hpp" #include "../include/Utils.hpp" #include #include #include #include #include namespace QuickMedia { static float floor(float v) { return (int)v; } static const float background_margin_horizontal = floor(5.0f * get_config().scale + 10.0f * get_config().spacing_scale); static const float padding_top_default = floor(10.0f * get_config().scale * get_config().spacing_scale); static const float padding_bottom_default = floor(15.0f * get_config().scale * get_config().spacing_scale); static const float background_margin_vertical = floor(4.0f * get_config().scale * get_config().spacing_scale); static const int character_size = get_config().search.font_size * get_config().scale * get_config().font_scale; static const int search_icon_padding_x = 10 * get_config().scale; SearchBar::SearchBar(mgl::Texture *plugin_logo, mgl::Shader *rounded_rectangle_shader, const std::string &placeholder, SearchBarType type) : onTextUpdateCallback(nullptr), onTextSubmitCallback(nullptr), onTextBeginTypingCallback(nullptr), text_autosearch_delay_ms(50), caret_visible(true), text("", *FontLoader::get_font(FontLoader::FontType::LATIN, character_size)), placeholder_text(placeholder, *FontLoader::get_font(FontLoader::FontType::LATIN, character_size)), background(mgl::vec2f(1.0f, 1.0f), 10.0f * get_config().scale, get_theme().selected_color, rounded_rectangle_shader), search_icon_sprite(TextureLoader::get_texture("images/search_icon.png")), updated_search(false), draw_logo(false), needs_update(true), typing(false), backspace_pressed(false), mouse_left_inside(false), pos(0.0f, 0.0f), prev_size(0.0f, 0.0f), type(type) { padding_top = padding_top_default; padding_bottom = padding_bottom_default; text.set_color(get_theme().text_color); placeholder_text.set_color(get_theme().placeholder_text_color); shade.set_color(get_theme().shade_color); if(plugin_logo && plugin_logo->is_valid()) plugin_logo_sprite.set_texture(plugin_logo); search_icon_sprite.set_color(get_theme().text_color); caret.set_color(get_theme().text_color); } void SearchBar::draw(mgl::Window &window, mgl::vec2f size, bool draw_background) { if(std::abs(size.x - prev_size.x) > 1.0f || std::abs(size.y - prev_size.y) > 1.0f) { needs_update = true; prev_size = size; } if(needs_update) { needs_update = false; onWindowResize(size); } if(draw_background) window.draw(shade); background.draw(window); const int caret_offset_y = character_size * 0.15f; const bool show_placeholder = text.get_string().empty(); if(type == SearchBarType::Password && !show_placeholder) { std::string masked_str(text.get_string().size(), '*'); mgl::Text masked_text(std::move(masked_str), *text.get_font()); masked_text.set_position(text.get_position()); window.draw(masked_text); caret.set_position(masked_text.get_position() + mgl::vec2f(masked_text.get_bounds().size.x, 0.0f).floor() + mgl::vec2f(0.0f, caret_offset_y)); } else { if(show_placeholder) { window.draw(placeholder_text); caret.set_position(placeholder_text.get_position() + mgl::vec2f(-caret.get_size().x, caret_offset_y)); } else { window.draw(text); caret.set_position(text.get_position() + mgl::vec2f(text.get_bounds().size.x, 0.0f).floor() + mgl::vec2f(0.0f, caret_offset_y)); } } if(caret_visible && is_editable()) window.draw(caret); if(draw_logo) window.draw(plugin_logo_sprite); if(type == SearchBarType::Search) window.draw(search_icon_sprite); } void SearchBar::on_event(mgl::Window &window, mgl::Event &event) { if(is_touch_enabled() && event.type == mgl::Event::MouseButtonPressed && event.mouse_button.button == mgl::Mouse::Left) { mgl::FloatRect box(background.get_position(), background.get_size()); if(box.contains(mgl::vec2f(event.mouse_button.x, event.mouse_button.y))) mouse_left_inside = true; else mouse_left_inside = false; } else if(is_touch_enabled() && event.type == mgl::Event::MouseButtonReleased && event.mouse_button.button == mgl::Mouse::Left) { mgl::FloatRect box(background.get_position(), background.get_size()); if(mouse_left_inside && box.contains(mgl::vec2f(event.mouse_button.x, event.mouse_button.y))) show_virtual_keyboard(); mouse_left_inside = false; } if(!editable) return; if(event.type == mgl::Event::KeyPressed && event.key.code == mgl::Keyboard::Backspace) backspace_pressed = true; else if(event.type == mgl::Event::KeyReleased && event.key.code == mgl::Keyboard::Backspace) backspace_pressed = false; if(event.type == mgl::Event::KeyPressed && event.key.code == mgl::Keyboard::V && event.key.control) { append_text(window.get_clipboard_string()); } if(event.type == mgl::Event::KeyPressed && event.key.code == mgl::Keyboard::D && event.key.control) { if(!text.get_string().empty()) { clear(); updated_search = true; time_since_search_update.restart(); } return; } const bool pressing_ctrl = (event.type == mgl::Event::KeyPressed && event.key.control); if(pressing_ctrl && (event.type != mgl::Event::TextEntered || event.text.codepoint != 13)) // Enter return; if(event.type == mgl::Event::TextEntered && (window.is_key_pressed(mgl::Keyboard::LControl) || window.is_key_pressed(mgl::Keyboard::RControl)) && !window.is_key_pressed(mgl::Keyboard::LAlt)) return; if(event.type == mgl::Event::TextEntered && event.text.codepoint != 8 && event.text.codepoint != 127) { // 8 = backspace, 127 = del onTextEntered(event.text); } else if(event.type == mgl::Event::KeyPressed && event.key.code == mgl::Keyboard::Backspace) { mgl::Event::TextEvent text_event; text_event.codepoint = 8; text_event.size = 1; text_event.str[0] = 8; text_event.str[1] = '\0'; onTextEntered(text_event); } } void SearchBar::update() { double elapsed_time_sec = time_since_search_update.get_elapsed_time_seconds(); double timeout_sec = (double)text_autosearch_delay_ms * 0.001; if(backspace_pressed) timeout_sec = 0.75; if(updated_search && elapsed_time_sec >= timeout_sec) { updated_search = false; if(onTextUpdateCallback) onTextUpdateCallback(text.get_string()); typing = false; } } void SearchBar::onWindowResize(const mgl::vec2f &size) { draw_logo = plugin_logo_sprite.get_texture() != nullptr; if(size.x < 400.0f) draw_logo = false; float font_height = character_size + 7.0f; float rect_height = floor(font_height + background_margin_vertical * 2.0f); const int x_pad = padding_x * get_config().scale * get_config().spacing_scale; float offset_x; if(draw_logo) { float one_line_height = floor(character_size + 8.0f + background_margin_vertical * 2.0f); auto texture_size = plugin_logo_sprite.get_texture()->get_size(); mgl::vec2f texture_size_f(texture_size.x, texture_size.y); mgl::vec2f new_size = wrap_to_size(texture_size_f, mgl::vec2f(200.0f, one_line_height)); plugin_logo_sprite.set_scale(get_ratio(texture_size_f, new_size)); plugin_logo_sprite.set_position(vec2f_floor(pos.x + x_pad, pos.y + padding_top + rect_height * 0.5f - plugin_logo_sprite.get_texture()->get_size().y * plugin_logo_sprite.get_scale().y * 0.5f)); offset_x = x_pad + new_size.x + floor(10.0f * get_config().spacing_scale); } else { offset_x = x_pad; } const float width = floor(size.x - offset_x - x_pad); background.set_size(mgl::vec2f(width, rect_height)); shade.set_size(mgl::vec2f(size.x, padding_top + rect_height + padding_bottom)); caret.set_size(vec2f_floor(2.0f * get_config().scale, character_size + floor(2.0f * get_config().scale))); const int search_padding = (type == SearchBarType::Search ? search_icon_padding_x : 0); background.set_position(mgl::vec2f(pos.x + offset_x, pos.y + padding_top)); shade.set_position(pos); mgl::vec2f text_position(pos.x + offset_x + background_margin_horizontal + search_padding + search_padding, pos.y + padding_top + rect_height * 0.5f - character_size * 0.7f); text.set_position(text_position.floor()); placeholder_text.set_position(text_position.floor()); mgl::vec2f texture_size = search_icon_sprite.get_texture()->get_size().to_vec2f(); mgl::vec2f new_size = wrap_to_size_y(texture_size, character_size); search_icon_sprite.set_scale(get_ratio(texture_size, new_size)); search_icon_sprite.set_position(background.get_position() + mgl::vec2f(search_padding, background.get_size().y * 0.5f - new_size.y * 0.5f).floor()); } void SearchBar::onTextEntered(const mgl::Event::TextEvent &text_event) { if(text_event.codepoint == 8) { // Backspace std::string str = text.get_string(); if(str.size() > 0) { // TODO: When it's possible to move the cursor, then check at cursor position instead of end of the string if(str[str.size() - 1] == '\n') needs_update = true; const size_t codepoint_start_index = mgl::utf8_get_start_of_codepoint((const unsigned char*)str.c_str(), str.size(), str.size() - 1); str.erase(codepoint_start_index); text.set_string(std::move(str)); if(!updated_search) { typing = true; if(onTextBeginTypingCallback) onTextBeginTypingCallback(); } updated_search = true; time_since_search_update.restart(); } } else if(text_event.codepoint == 13) { // Return backspace_pressed = false; if(onTextSubmitCallback) onTextSubmitCallback(text.get_string()); } else if(text_event.codepoint > 31) { // Non-control character append_text(std::string(text_event.str, text_event.size)); } else if(text_event.codepoint == '\n') needs_update = true; } void SearchBar::clear() { text.set_string(""); needs_update = true; updated_search = false; backspace_pressed = false; } void SearchBar::set_text(const std::string &text, bool update_search) { clear(); append_text(text); if(!update_search) { needs_update = false; updated_search = false; } } void SearchBar::append_text(const std::string &text_to_add) { if(text_to_add.empty()) return; text.append_string(text_to_add); if(!updated_search) { typing = true; if(onTextBeginTypingCallback) onTextBeginTypingCallback(); } updated_search = true; time_since_search_update.restart(); backspace_pressed = false; if(text_to_add.find('\n') != std::string::npos) needs_update = true; } void SearchBar::set_position(mgl::vec2f pos) { if(std::abs(this->pos.x - pos.x) > 1.0f || std::abs(this->pos.y - pos.y) > 1.0f) { this->pos = pos; needs_update = true; } } void SearchBar::set_editable(bool editable) { this->editable = editable; } bool SearchBar::is_editable() const { return editable; } float SearchBar::getBottom() const { return getBottomWithoutShadow() + 5.0f;//background_shadow.get_size().y; } float SearchBar::getBottomWithoutShadow() const { float font_height = character_size + 7.0f; return floor(font_height + background_margin_vertical * 2.0f + padding_top + padding_bottom); } const std::string& SearchBar::get_text() const { return text.get_string(); } bool SearchBar::is_empty() const { return text.get_string().empty(); } }