aboutsummaryrefslogtreecommitdiff
path: root/include/Text.hpp
blob: 72fdd66f27a6a9b5a5a8077f20ea28860ea5f291 (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
#pragma once

#include "NetUtils.hpp"
#include <SFML/Graphics/VertexArray.hpp>
#include <SFML/Graphics/VertexBuffer.hpp>
#include <SFML/System/String.hpp>
#include <SFML/System/Clock.hpp>
#include <vector>
#include "types.hpp"
#include <assert.h>

namespace sf {
    class Font;
    class Event;
    class RenderTarget;
}

namespace QuickMedia
{
    struct StringViewUtf32 {
        const u32 *data;
        size_t size;

        StringViewUtf32() : data(nullptr), size(0) {}
        StringViewUtf32(const u32 *data, usize size) : data(data), size(size) {}

        size_t find(const StringViewUtf32 &other, size_t offset = 0) const;

        u32 operator [] (usize index) const {
            assert(index < size);
            return data[index];
        }
    };

    struct TextElement
    {
        enum class Type
        {
            TEXT
        };

        enum class TextType {
            LATIN,
            CJK,
            SYMBOL,
            EMOJI
        };
        
        TextElement() {}
        TextElement(const StringViewUtf32 &_text, Type _type) : text(_text), type(_type), text_type(TextType::LATIN) {}
        
        StringViewUtf32 text;
        sf::Vector2f position;
        Type type;
        //bool ownLine; // Currently only used for emoji, to make emoji bigger when it's the only thing on a line
        TextType text_type;
    };

    struct VertexRef {
        int vertices_index;     // index to |vertices| VertexArray
        int index;              // index within vertices[vertices_index]
        int line;
        sf::Uint32 codepoint;
    };
    
    class Text
    {
    public:
        Text(bool bold_font);
        Text(sf::String str, bool bold_font, unsigned int characterSize, float maxWidth, bool highlight_urls = false);
        
        void setString(const sf::String &str);
        const sf::String& getString() const;
        void appendText(const sf::String &str);
        
        void setPosition(float x, float y);
        void setPosition(const sf::Vector2f &position);
        sf::Vector2f getPosition() const;
        
        void setMaxWidth(float maxWidth);
        float getMaxWidth() const;
        
        void setCharacterSize(unsigned int characterSize);
        unsigned int getCharacterSize() const;

        void replace(size_t start_index, size_t length, const sf::String &insert_str);
        int getCaretIndex() const;

        
        void setFillColor(sf::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(const sf::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(sf::RenderTarget &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
        };

        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 splitTextByFont();

        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;
        sf::Uint32 get_vertex_codepoint(int index) const;
    private:
        sf::String str; // TODO: Remove this for non-editable text??? also replace with std::string? then we get more efficient editing of text
        const bool bold_font;
        unsigned int characterSize;
        std::array<sf::VertexArray, 4> vertices;
        std::array<sf::VertexBuffer, 4> vertex_buffers;
        float maxWidth;
        sf::Vector2f position;
        sf::Color color;
        bool dirty;
        bool dirtyText;
        bool dirtyCaret;
        bool editable;
        bool highlight_urls;
        CaretMoveDirection caretMoveDirection;
        sf::FloatRect boundingBox;
        int num_lines;
        float lineSpacing;
        float characterSpacing;
        std::vector<TextElement> textElements;
        
        int caretIndex;
        float caret_offset_x;
        sf::Vector2f caretPosition;
        sf::Vector2u renderTargetSize;

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

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