CYD-UI
A C++ library for building native graphic user interfaces
Loading...
Searching...
No Matches
text.cppm
Go to the documentation of this file.
1//
2// Created by castle on 8/16/24.
3//
4
5module;
7
8export module cydui.std.input.text;
9
10import fabric.logging;
11import cydui;
12
13namespace stdui::input {
15
16 };
17 export COMPONENT(
18 text, { std::string* text; } STATE { int caret_pos = 0; };
19 ATTRIBUTE(on_enter, std::function<void()>){[] {}};
20 ATTRIBUTE(on_escape, std::function<void()>){[] {}};
21 ) {
22 cydui::provide_context<TextInputContext> text_input_ctx{};
23
24 CHILDREN {
25 return {
26 text_input_ctx > with_context {
27 $content
28 }
29 };
30 }
31
32 FRAGMENT {
33 // fragment.draw<vg::rect>().w($width).h($height).fill("#222222"_color);
34 fragment.append(build_text(*props.text).fill("#FFFFFF"_color));
35
36 if (state.focused) {
37 const auto [x, y, w, h] = build_text(props.text->substr(0, state.caret_pos)).get_footprint();
38 fragment.draw<vg::rectangle>().x(x + w - 1_px).y(y - 3_px).w(2_px).h(h + 3_px).fill("#FFFFFF"_color);
39 }
40 }
41
43 if (ev.keysym.code == SDLK_LEFT) {
44 state.caret_pos = advance_from(state.caret_pos, -1);
45 } else if (ev.keysym.code == SDLK_RIGHT) {
46 state.caret_pos = advance_from(state.caret_pos);
47 } else if (ev.keysym.code == SDLK_HOME) {
48 state.caret_pos = 0;
49 } else if (ev.keysym.code == SDLK_END) {
50 state.caret_pos = props.text->size();
51 } else if (ev.keysym.code == SDLK_RETURN) {
52 component.on_enter_();
53 } else if (ev.keysym.code == SDLK_ESCAPE) {
54 component.on_escape_();
55 } else if (ev.keysym.code == SDLK_BACKSPACE) {
56 if (state.caret_pos == static_cast<int>(props.text->size())) {
57 while (!props.text->empty()) {
58 const unsigned char c = (*props.text)[props.text->size() - 1];
59 props.text->pop_back();
60 --state.caret_pos;
61 if ((c & 0x80) == 0) { // ASCII character (1 byte)
62 break;
63 } else if ((c & 0xC0) == 0x80) { // Continuation byte (part of a multi-byte character)
64 continue;
65 } else { // Leading byte of a UTF-8 character
66 break;
67 }
68 // state.text = state.text.substr(0, state.text.size() - 1);
69 }
70 } else {
71 while (!props.text->empty() && state.caret_pos > 0) {
72 const unsigned char c = (*props.text)[state.caret_pos - 1];
73 props.text->erase(state.caret_pos - 1, 1);
74 --state.caret_pos;
75 if ((c & 0x80) == 0) { // ASCII character (1 byte)
76 break;
77 } else if ((c & 0xC0) == 0x80) { // Continuation byte (part of a multi-byte character)
78 continue;
79 } else { // Leading byte of a UTF-8 character
80 break;
81 }
82 // state.text = state.text.substr(0, state.text.size() - 1);
83 }
84 }
85 }
86 state.mark_dirty();
87 }
88
90 if (not ev.compositing_event) {
91 if (state.caret_pos == static_cast<int>(props.text->size())) {
92 props.text->append(ev.text);
93 } else {
94 props.text->insert(state.caret_pos, ev.text);
95 }
96 state.caret_pos += static_cast<int>(ev.text.size());
97 state.mark_dirty();
98 }
99 }
100
102 int min_distance = std::numeric_limits<int>::max();
103 auto prev = build_text(props.text->substr(0, advance_from(0))).get_footprint();
104 {
105 const auto d1 = std::abs(x.value_as_base_unit() - prev.x.value_as_base_unit());
106 const auto d2 = std::abs(x.value_as_base_unit() - (prev.x.value_as_base_unit() + prev.w.value_as_base_unit()));
107 if (d1 < d2) {
108 state.caret_pos = 0;
109 min_distance = d1;
110 } else {
111 state.caret_pos = 1;
112 min_distance = d2;
113 }
114 }
115 for (int i = 2; i < static_cast<int>(props.text->size()); i = advance_from(i)) {
116 const auto curr = build_text(props.text->substr(0, i)).get_footprint();
117 {
118 const auto d1 = std::abs(x.value_as_base_unit() - (prev.x + prev.w).value_as_base_unit());
119 const auto d2 = std::abs(x.value_as_base_unit() - (curr.x + curr.w).value_as_base_unit());
120 if (d1 > min_distance && d2 > min_distance) {
121 break;
122 }
123 if (d1 < d2) {
124 state.caret_pos = advance_from(i, -1);
125 min_distance = d1;
126 } else {
127 state.caret_pos = i;
128 min_distance = d2;
129 }
130 }
131 prev = curr;
132 }
133 state.mark_dirty();
134 }
135
136 private:
137 int advance_from(int index, int n = 1) const {
138 if (n > 0) {
139 while (n-- > 0) {
140 if (((*props.text)[index] & 0xC0) == 0xC0) {
141 ++index;
142 while (((*props.text)[index] & 0xC0) == 0x80 && index < static_cast<int>(props.text->size())
143 ) {
144 ++index;
145 }
146 index = std::min(index, static_cast<int>(props.text->size()));
147 } else {
148 ++index;
149 index = std::min(index, static_cast<int>(props.text->size()));
150 }
151 }
152 } else if (n < 0) {
153 while (n++ < 0) {
154 while (((*props.text)[index - 1] & 0xC0) == 0x80 && index > 0) {
155 --index;
156 }
157 --index;
158 index = std::max(index, 0);
159 }
160 }
161 return index;
162 }
163
164 private:
165 vg::text build_text(const std::string& s) const {
166 return vg::text{s}.x(0).y(25).font_family("Helvetica");
167 }
168 };
169}
#define ON_KEY_PRESS
#define CHILDREN
#define FRAGMENT
#define ON_BUTTON_PRESS
#define COMPONENT(NAME,...)
#define STATE
#define ON_TEXT_INPUT
#define ATTRIBUTE(NAME,...)