aboutsummaryrefslogtreecommitdiff
path: root/src/gui/Entry.cpp
blob: 1cb8d1652d29d731c5dc364407c17f96d9fa227d (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
#include "../../include/gui/Entry.hpp"
#include "../../include/gui/Utils.hpp"
#include "../../include/Theme.hpp"
#include <mglpp/graphics/Rectangle.hpp>
#include <mglpp/window/Window.hpp>
#include <mglpp/window/Event.hpp>
#include <mglpp/system/FloatRect.hpp>
#include <mglpp/system/Utf8.hpp>
#include <optional>

namespace gsr {
    static const float padding_top_scale = 0.004629f;
    static const float padding_bottom_scale = 0.004629f;
    static const float padding_left_scale = 0.007f;
    static const float padding_right_scale = 0.007f;
    static const float border_scale = 0.0015f;
    static const float caret_width_scale = 0.001f;

    Entry::Entry(mgl::Font *font, const char *text, float max_width) : text("", *font), max_width(max_width) {
        this->text.set_color(get_theme().text_color);
        set_text(text);
    }

    bool Entry::on_event(mgl::Event &event, mgl::Window &window, mgl::vec2f offset) {
        if(!visible)
            return true;

        if(event.type == mgl::Event::MouseButtonPressed && event.mouse_button.button == mgl::Mouse::Left) {
            selected = mgl::FloatRect(position + offset, get_size()).contains({ (float)event.mouse_button.x, (float)event.mouse_button.y });
        } else if(event.type == mgl::Event::KeyPressed && selected) {
            if(event.key.code == mgl::Keyboard::Backspace && !text.get_string().empty()) {
                std::string str = text.get_string();
                const size_t prev_index = mgl::utf8_get_start_of_codepoint((const unsigned char*)str.c_str(), str.size(), str.size());
                str.erase(prev_index, std::string::npos);
                set_text(std::move(str));
            } else if(event.key.code == mgl::Keyboard::V && event.key.control) {
                std::string clipboard_text = window.get_clipboard_string();
                std::string str = text.get_string();
                str += clipboard_text;
                set_text(std::move(str));
            }
        } else if(event.type == mgl::Event::TextEntered && selected && event.text.codepoint >= 32) {
            std::string str = text.get_string();
            str.append(event.text.str, event.text.size);
            set_text(std::move(str));
        }
        return true;
    }

    void Entry::draw(mgl::Window &window, mgl::vec2f offset) {
        if(!visible)
            return;

        const mgl::vec2f draw_pos = position + offset;

        const int padding_top = padding_top_scale * get_theme().window_height;
        const int padding_left = padding_left_scale * get_theme().window_height;

        mgl::Rectangle background(get_size());
        background.set_position(draw_pos.floor());
        background.set_color(selected ? mgl::Color(0, 0, 0, 255) : mgl::Color(0, 0, 0, 120));
        window.draw(background);

        if(selected) {
            const int border_size = std::max(1.0f, border_scale * get_theme().window_height);
            draw_rectangle_outline(window, draw_pos.floor(), get_size().floor(), get_theme().tint_color, border_size);

            const int caret_width = std::max(1.0f, caret_width_scale * get_theme().window_height);
            mgl::Rectangle caret({(float)caret_width, text.get_bounds().size.y});
            caret.set_position((draw_pos + mgl::vec2f(padding_left + caret_offset_x, padding_top)).floor());
            caret.set_color(mgl::Color(255, 255, 255));
            window.draw(caret);
        }

        text.set_position((draw_pos + mgl::vec2f(padding_left, get_size().y * 0.5f - text.get_bounds().size.y * 0.5f)).floor());
        window.draw(text);
    }

    mgl::vec2f Entry::get_size() {
        if(!visible)
            return {0.0f, 0.0f};

        const int padding_top = padding_top_scale * get_theme().window_height;
        const int padding_bottom = padding_bottom_scale * get_theme().window_height;
        return { max_width, text.get_bounds().size.y + padding_top + padding_bottom };
    }

    void Entry::set_text(std::string str) {
        if(!validate_handler || validate_handler(str)) {
            text.set_string(std::move(str));
            caret_offset_x = text.find_character_pos(99999).x - this->text.get_position().x;
        }
    }

    const std::string& Entry::get_text() const {
        return text.get_string();
    }

    static bool is_number(uint8_t c) {
        return c >= '0' && c <= '9';
    }

    static std::optional<int> to_integer(const std::string &str) {
        if(str.empty())
            return std::nullopt;

        size_t i = 0;
        const bool negative = str[0] == '-';
        if(negative)
            i = 1;

        int number = 0;
        for(; i < str.size(); ++i) {
            if(!is_number(str[i]))
                return false;

            const int new_number = number * 10 + (str[i] - '0');
            if(new_number < number)
                return std::nullopt; // Overflow
            number = new_number;
        }
        
        if(negative)
            number = -number;

        return number;
    }

    EntryValidateHandler create_entry_validator_integer_in_range(int min, int max) {
        return [min, max](std::string &str) {
            if(str.empty())
                return true;

            std::optional<int> number = to_integer(str);
            if(!number)
                return false;

            if(number.value() < min)
                str = std::to_string(min);
            else if(number.value() > max)
                str = std::to_string(max);
            return true;
        };
    }
}