#pragma once #include "NetUtils.hpp" #include #include #include #include #include #include #include #include #include namespace mgl { class Font; class Event; class Window; } namespace QuickMedia { static constexpr size_t FONT_ARRAY_SIZE = 6; enum FormattedTextFlag : uint8_t { FORMATTED_TEXT_FLAG_NONE = 0, //FORMATTED_TEXT_FLAG_BOLD = 1 << 0, FORMATTED_TEXT_FLAG_CODE = 1 << 1, FORMATTED_TEXT_FLAG_COLOR = 1 << 2 }; struct TextElement { enum class Type { TEXT, FORMAT_START, FORMAT_END, IMAGE }; enum class TextType { TEXT, EMOJI }; TextElement() {} void create_text(std::string_view text) { this->text = text; text_type = TextType::TEXT; type = Type::TEXT; } // If size is {0, 0} then the image is drawn at its original size void create_image(std::string url, bool local, mgl::vec2i size, std::string alt) { this->url = std::move(url); this->alt = std::move(alt); this->local = local; this->size = size; type = Type::IMAGE; } // TODO: Remove some fields // TODO: union grouped std::string_view text; TextType text_type = TextType::TEXT; mgl::Color color = mgl::Color(255, 255, 255, 255); uint8_t text_flags = FORMATTED_TEXT_FLAG_NONE; // FormattedTextFlag // TODO: Remove these std::string url; std::string alt; bool local = false; mgl::vec2i pos; mgl::vec2i size; int vertex_ref_index = 0; // Note: only used temporary within updateGeometry int text_num_bytes = 0; // Note: only used temporary within updateGeometry Type type; }; struct VertexRef { int vertices_index; // index to |vertices| VertexArray int index; // index within vertices[vertices_index] int line; int text_num_bytes; uint32_t codepoint; }; class Text { public: Text(std::string str, bool bold_font, unsigned int characterSize, float maxWidth, bool highlight_urls = false); void setString(std::string str); const std::string& getString() const; void appendText(const std::string &str); // size = {0, 0} = keep original image size static std::string formatted_image(const std::string &url, bool local, mgl::vec2i size, const std::string &alt); // text_flags is bit-or of FormattedTextFlag static std::string formatted_text(const std::string &text, mgl::Color color, uint8_t text_flags); void insert_text_at_caret_position(const std::string &str); // Set to 0 to disable max lines void set_max_lines(int max_lines); static std::string to_printable_string(const std::string &str); void set_position(float x, float y); void set_position(const mgl::vec2f &position); mgl::vec2f get_position() const; void setMaxWidth(float maxWidth); float getMaxWidth() const; void setCharacterSize(unsigned int characterSize); unsigned int get_character_size() const; void replace(size_t start_index, size_t length, const std::string &insert_str); int getCaretIndex() const; int getNumLines() const; void set_color(mgl::Color color, bool force_color = false); void setLineSpacing(float lineSpacing); void setCharacterSpacing(float characterSpacing); void setEditable(bool editable); bool isEditable() const; // Note: only call after initial updateGeometry or draw. TODO: Automatically do this internally void moveCaretToEnd(); // Note: won't update until @draw is called float getWidth() const; // Note: won't update until @draw is called float getHeight() const; void processEvent(mgl::Window &window, const mgl::Event &event); // Performs culling. @updateGeometry is called even if text is not visible if text is dirty, because updateGeometry might change the dimension of the text and make is visible. // Returns true if text was drawn on screen (if text is within window borders) bool draw(mgl::Window &target); void updateGeometry(bool update_even_if_not_dirty = false); bool single_line_edit = false; private: enum class CaretMoveDirection : uint8_t { NONE, UP, DOWN, HOME, END, LEFT, RIGHT, LEFT_WORD, RIGHT_WORD }; Text(); void updateCaret(); int getStartOfLine(int startIndex) const; int getEndOfLine(int startIndex) const; int getStartOfWord(int startIndex) const; int getEndOfWord(int startIndex) const; int getPreviousLineClosestPosition(int startIndex) const; int getNextLineClosestPosition(int startIndex) const; static void split_text_by_type(std::vector &text_elements, std::string_view str, float vspace); void split_text_by_type(); float font_get_real_height(mgl::Font *font); float get_text_quad_left_side(const VertexRef &vertex_ref) const; float get_text_quad_right_side(const VertexRef &vertex_ref) const; float get_text_quad_top_side(const VertexRef &vertex_ref) const; float get_text_quad_bottom_side(const VertexRef &vertex_ref) const; float get_text_quad_height(const VertexRef &vertex_ref) const; void move_vertex_lines_by_largest_items(int vertices_linear_end); // If the index is past the end, then the caret offset is the right side of the last character, rather than the left side float get_caret_offset_by_caret_index(int index) const; VertexRef& get_vertex_ref_clamp(int index); // Takes into consideration if index is the last vertex and the last vertex is a newline, then it should be on its own line int get_vertex_line(int index) const; uint32_t get_vertex_codepoint(int index) const; size_t get_string_index_from_caret_index(size_t caret_index) const; private: std::string str; // TODO: Remove this for non-editable text??? bool bold_font; unsigned int characterSize; std::array, FONT_ARRAY_SIZE> vertices; std::array vertex_buffers; float maxWidth; mgl::vec2f position; mgl::Color color; bool force_color = false; bool dirty; bool dirtyText; bool dirtyCaret; bool editable; bool highlight_urls; CaretMoveDirection caretMoveDirection; mgl::FloatRect boundingBox; int num_lines; float lineSpacing; float characterSpacing; std::vector textElements; int caretIndex; float caret_offset_x; mgl::vec2f caretPosition; int max_lines = 0; std::vector vertices_linear; // TODO: Use textElements instead std::vector url_ranges; }; }