#include "../include/SearchBar.hpp" #include "../include/Scale.hpp" #include "../include/ResourceLoader.hpp" #include "../include/Utils.hpp" #include #include #include #include #include // 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 float background_margin_horizontal = 10.0f + std::floor(5.0f * QuickMedia::get_ui_scale()); static const float PADDING_HORIZONTAL = std::floor(25.0f * QuickMedia::get_ui_scale()); static const float padding_top = std::floor(10.0f * QuickMedia::get_ui_scale()); static const float padding_bottom = std::floor(15.0f * QuickMedia::get_ui_scale()); static const float background_margin_vertical = std::floor(4.0f * QuickMedia::get_ui_scale()); namespace QuickMedia { SearchBar::SearchBar(sf::Texture *plugin_logo, const std::string &placeholder, bool input_masked) : onTextUpdateCallback(nullptr), onTextSubmitCallback(nullptr), onTextBeginTypingCallback(nullptr), onAutocompleteRequestCallback(nullptr), text_autosearch_delay(0), autocomplete_search_delay(0), caret_visible(true), text(placeholder, *FontLoader::get_font(FontLoader::FontType::LATIN), std::floor(16 * get_ui_scale())), autocomplete_text("", *FontLoader::get_font(FontLoader::FontType::LATIN), std::floor(16 * get_ui_scale())), background(sf::Vector2f(1.0f, 1.0f), 10.0f, 10), placeholder_str(placeholder), show_placeholder(true), updated_search(false), updated_autocomplete(false), draw_logo(false), needs_update(true), input_masked(input_masked), typing(false), backspace_pressed(false), mouse_left_inside(false), vertical_pos(0.0f) { text.setFillColor(text_placeholder_color); autocomplete_text.setFillColor(text_placeholder_color); background.setFillColor(front_color); //background.setCornersRadius(5); background_shadow.setFillColor(sf::Color(23, 25, 27)); //background_shadow.setPosition(background.getPosition() + sf::Vector2f(5.0f, 5.0f)); shade.setFillColor(sf::Color(33, 37, 44)); //background.setOutlineThickness(1.0f); //background.setOutlineColor(sf::Color(13, 15, 17)); if(plugin_logo && plugin_logo->getNativeHandle() != 0) plugin_logo_sprite.setTexture(*plugin_logo, true); } void SearchBar::draw(sf::RenderWindow &window, bool draw_shadow) { if(needs_update) { needs_update = false; sf::Vector2u window_size = window.getSize(); onWindowResize(sf::Vector2f(window_size.x, window_size.y)); } (void)draw_shadow; //if(draw_shadow) // window.draw(background_shadow); window.draw(shade); window.draw(background); // TODO: Render starting from the character after text length window.draw(autocomplete_text); if(input_masked && !show_placeholder) { std::string masked_str(text.getString().getSize(), '*'); sf::Text masked_text(std::move(masked_str), *text.getFont(), text.getCharacterSize()); masked_text.setPosition(text.getPosition()); window.draw(masked_text); caret.setPosition(masked_text.findCharacterPos(masked_text.getString().getSize()) + sf::Vector2f(0.0f, 2.0f)); } else { window.draw(text); if(show_placeholder || text.getString().isEmpty()) caret.setPosition(text.getPosition() - sf::Vector2f(2.0f, 0.0f) + sf::Vector2f(0.0f, 2.0f)); else caret.setPosition(text.findCharacterPos(text.getString().getSize()) + sf::Vector2f(0.0f, 2.0f)); } if(caret_visible) window.draw(caret); if(draw_logo) window.draw(plugin_logo_sprite); } void SearchBar::on_event(sf::Event &event) { 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) backspace_pressed = false; if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::V && event.key.control) { auto clipboard = sf::Clipboard::getString().toUtf8(); append_text(std::string(clipboard.begin(), clipboard.end())); } if(event.type == sf::Event::TextEntered && event.text.unicode != 8 && event.text.unicode != 127) // 8 = backspace, 127 = del 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.getPosition(), background.getSize()); 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.getPosition(), background.getSize()); if(mouse_left_inside && box.contains(event.mouseButton.x, event.mouseButton.y)) show_virtual_keyboard(); mouse_left_inside = false; } } void SearchBar::update() { sf::Int32 elapsed_time = time_since_search_update.getElapsedTime().asMilliseconds(); int timeout = text_autosearch_delay; if(backspace_pressed) timeout = 750; if(updated_search && elapsed_time >= timeout) { updated_search = false; auto u8 = text.getString().toUtf8(); std::string *u8_str = (std::string*)&u8; if(show_placeholder) u8_str->clear(); if(onTextUpdateCallback) onTextUpdateCallback(*u8_str); typing = false; } else if(updated_autocomplete && elapsed_time >= autocomplete_search_delay) { updated_autocomplete = false; if(!show_placeholder && onAutocompleteRequestCallback) { auto u8 = text.getString().toUtf8(); std::string *u8_str = (std::string*)&u8; onAutocompleteRequestCallback(*u8_str); } } } void SearchBar::onWindowResize(const sf::Vector2f &window_size) { draw_logo = plugin_logo_sprite.getTexture() != nullptr; float padding_horizontal = PADDING_HORIZONTAL; if(window_size.x - padding_horizontal * 2.0f < 400.0f) { padding_horizontal = 0.0f; draw_logo = false; } float font_height = text.getCharacterSize() + 7.0f; float rect_height = std::floor(font_height + background_margin_vertical * 2.0f); float offset_x = padding_horizontal; if(draw_logo) { float one_line_height = std::floor(text.getCharacterSize() + 8.0f + background_margin_vertical * 2.0f); auto texture_size = plugin_logo_sprite.getTexture()->getSize(); sf::Vector2f texture_size_f(texture_size.x, texture_size.y); sf::Vector2f new_size = wrap_to_size(texture_size_f, sf::Vector2f(200.0f, one_line_height)); plugin_logo_sprite.setScale(get_ratio(texture_size_f, new_size)); plugin_logo_sprite.setPosition(25.0f, padding_top + vertical_pos + rect_height * 0.5f - plugin_logo_sprite.getTexture()->getSize().y * plugin_logo_sprite.getScale().y * 0.5f); offset_x = 25.0f + new_size.x + 25.0f; } const float width = std::floor(window_size.x - offset_x - 25.0f); background.setSize(sf::Vector2f(width, rect_height)); shade.setSize(sf::Vector2f(window_size.x, padding_top + rect_height + padding_bottom)); caret.setSize(sf::Vector2f(std::floor(2.0f * get_ui_scale()), text.getCharacterSize() + std::floor(2.0f * get_ui_scale()))); background_shadow.setSize(sf::Vector2f(window_size.x, 5.0f)); background.setPosition(offset_x, padding_top + vertical_pos); shade.setPosition(0.0f, vertical_pos); background_shadow.setPosition(0.0f, std::floor(shade.getSize().y + vertical_pos)); sf::Vector2f font_position(std::floor(offset_x + background_margin_horizontal), std::floor(padding_top + background_margin_vertical + vertical_pos)); autocomplete_text.setPosition(font_position); text.setPosition(font_position); } void SearchBar::onTextEntered(sf::Uint32 codepoint) { if(codepoint == 8 && !show_placeholder) { // Backspace sf::String str = text.getString(); if(str.getSize() > 0) { // TODO: When it's possible to move the cursor, then check at cursor position instead of end of the string if(str[str.getSize() - 1] == '\n') needs_update = true; str.erase(str.getSize() - 1, 1); text.setString(str); if(str.getSize() == 0) { show_placeholder = true; text.setString(placeholder_str); text.setFillColor(text_placeholder_color); autocomplete_text.setString(""); } else { clear_autocomplete_if_text_not_substring(); } if(!updated_search) { typing = true; if(onTextBeginTypingCallback) onTextBeginTypingCallback(); } updated_search = true; updated_autocomplete = true; time_since_search_update.restart(); } } else if(codepoint == 13) { // Return backspace_pressed = false; if(onTextSubmitCallback) { auto u8 = text.getString().toUtf8(); std::string *u8_str = (std::string*)&u8; if(show_placeholder) u8_str->clear(); onTextSubmitCallback(*u8_str); } } else if(codepoint > 31) { // Non-control character append_text(sf::String(codepoint)); } else if(codepoint == '\n') needs_update = true; } void SearchBar::clear() { if(show_placeholder) return; show_placeholder = true; text.setString(placeholder_str); text.setFillColor(text_placeholder_color); autocomplete_text.setString(""); needs_update = true; updated_search = false; updated_autocomplete = false; backspace_pressed = false; } void SearchBar::append_text(const std::string &text_to_add) { if(text_to_add.empty()) return; if(show_placeholder) { show_placeholder = false; text.setString(""); text.setFillColor(sf::Color::White); } sf::String str = text.getString(); str += text_to_add; text.setString(str); clear_autocomplete_if_text_not_substring(); if(!updated_search) { typing = true; if(onTextBeginTypingCallback) onTextBeginTypingCallback(); } updated_search = true; updated_autocomplete = true; time_since_search_update.restart(); backspace_pressed = false; if(str[str.getSize() - 1] == '\n') needs_update = true; } bool SearchBar::is_cursor_at_start_of_line() const { // TODO: When it's possible to move the cursor, then check at the cursor position instead of end of the string const sf::String &str = text.getString(); return show_placeholder || str.getSize() == 0 || str[str.getSize() - 1] == '\n'; } void SearchBar::set_to_autocomplete() { const sf::String &autocomplete_str = autocomplete_text.getString(); if(!autocomplete_str.isEmpty()) { if(show_placeholder) { show_placeholder = false; text.setString(""); text.setFillColor(sf::Color::White); } text.setString(autocomplete_str); if(!updated_search) { typing = true; if(onTextBeginTypingCallback) onTextBeginTypingCallback(); } updated_search = true; updated_autocomplete = true; time_since_search_update.restart(); needs_update = true; } } void SearchBar::set_autocomplete_text(const std::string &text) { autocomplete_text.setString(text); } void SearchBar::set_vertical_position(float vertical_pos) { if(std::abs(this->vertical_pos - vertical_pos) > 1.0f) { this->vertical_pos = vertical_pos; needs_update = true; } } void SearchBar::set_background_color(sf::Color color) { background.setFillColor(color); } void SearchBar::clear_autocomplete_if_text_not_substring() { const sf::String &text_str = text.getString(); const sf::String &autocomplete_str = autocomplete_text.getString(); if(text_str.getSize() > autocomplete_str.getSize()) { autocomplete_text.setString(""); return; } for(size_t i = 0; i < autocomplete_str.getSize(); ++i) { if(text_str[i] != autocomplete_str[i]) { autocomplete_text.setString(""); return; } } } void SearchBar::clear_autocomplete_if_last_char_not_substr() { const sf::String &text_str = text.getString(); const sf::String &autocomplete_str = autocomplete_text.getString(); if(text_str.isEmpty() || text_str.getSize() > autocomplete_str.getSize()) { autocomplete_text.setString(""); return; } if(autocomplete_str[text_str.getSize() - 1] != text_str[text_str.getSize() - 1]) { autocomplete_text.setString(""); return; } } float SearchBar::getBottom() const { return getBottomWithoutShadow() + 5.0f;//background_shadow.getSize().y; } float SearchBar::getBottomWithoutShadow() const { float font_height = text.getCharacterSize() + 7.0f; return std::floor(font_height + background_margin_vertical * 2.0f) + padding_top + padding_bottom; } std::string SearchBar::get_text() const { if(show_placeholder) return ""; return text.getString(); } }