aboutsummaryrefslogtreecommitdiff
path: root/include/Text.hpp
blob: 89aba9991c62ed293410f12919be2c53b4280372 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
#pragma once

#include "NetUtils.hpp"
#include <mglpp/system/FloatRect.hpp>
#include <mglpp/system/vec.hpp>
#include <mglpp/graphics/Color.hpp>
#include <mglpp/graphics/VertexBuffer.hpp>
#include <mglpp/graphics/Vertex.hpp>
#include <mglpp/system/Clock.hpp>
#include <vector>
#include <string_view>
#include <array>

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<TextElement> &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<std::vector<mgl::Vertex>, FONT_ARRAY_SIZE> vertices;
        std::array<mgl::VertexBuffer, FONT_ARRAY_SIZE> 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<TextElement> textElements;
        
        int caretIndex;
        float caret_offset_x;
        mgl::vec2f caretPosition;

        int max_lines = 0;
        std::vector<VertexRef> vertices_linear; // TODO: Use textElements instead
        std::vector<Range> url_ranges;
    };
}