aboutsummaryrefslogtreecommitdiff
path: root/include/Text.hpp
blob: 1533380777013510d06147645c6aafd1726ebda7 (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
#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>
#include "types.hpp"
#include <assert.h>

namespace mgl {
    class Font;
    class Event;
    class Window;
}

namespace QuickMedia
{
    struct TextElement
    {
        enum class Type {
            TEXT,
            IMAGE
        };

        enum class TextType {
            LATIN,
            CJK,
            SYMBOL,
            EMOJI
        };
        
        TextElement() {}

        void create_text(std::string_view text) {
            this->text = text;
            text_type = TextType::LATIN;
            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) {
            this->url = std::move(url);
            this->local = local;
            this->size = size;
            type = Type::IMAGE;
        }
        
        // TODO: union grouped
        std::string_view text;
        TextType text_type = TextType::LATIN;

        // TODO: Remove these
        std::string url;
        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);
        Text(const Text &other);
        Text& operator=(const Text&);
        
        void setString(std::string str);
        const std::string& getString() const;
        void appendText(const std::string &str);
        // size = {0, 0} = keep original image size
        void append_image(const std::string &url, bool local, mgl::vec2i size);
        static std::string formatted_image(const std::string &url, bool local, mgl::vec2i size);
        static std::string formatted_text(const std::string &text, mgl::Color color, bool bold);
        void insert_text_at_caret_position(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);
        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 : u8
        {
            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;

        void split_text_by_type(std::vector<TextElement> &text_elements, const std::string &str);

        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;
        // 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??? also replace with std::string? then we get more efficient editing of text
        bool bold_font;
        unsigned int characterSize;
        std::array<std::vector<mgl::Vertex>, 5> vertices;
        std::array<mgl::VertexBuffer, 5> vertex_buffers;
        float maxWidth;
        mgl::vec2f position;
        mgl::Color color;
        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;
        mgl::vec2u renderTarget_size;

        std::vector<VertexRef> vertices_linear; // TODO: Use textElements instead

        std::vector<Range> url_ranges;
    };
}