1 #include <cppurses/widget/widgets/text_display.hpp>
2
3 #include <algorithm>
4 #include <cstddef>
5 #include <iterator>
6 #include <string>
7 #include <utility>
8
9 #include <signals/signal.hpp>
10
11 #include <cppurses/painter/attribute.hpp>
12 #include <cppurses/painter/glyph_string.hpp>
13 #include <cppurses/painter/painter.hpp>
14 #include <cppurses/widget/point.hpp>
15
16 namespace cppurses {
17
Text_display(Glyph_string contents)18 Text_display::Text_display(Glyph_string contents)
19 : contents_{std::move(contents)} {
20 this->set_name("Text_display");
21 }
22
23 // This call to update_display is required here, and not in paint_event.
24 // Could probably be refactored so this can be in paint_event, more efficient.
update()25 void Text_display::update() {
26 this->update_display();
27 Widget::update();
28 }
29
set_contents(Glyph_string text)30 void Text_display::set_contents(Glyph_string text) {
31 contents_ = std::move(text);
32 this->update();
33 top_line_ = 0;
34 this->cursor.set_position({0, 0});
35 contents_modified(contents_);
36 }
37
insert(Glyph_string text,std::size_t index)38 void Text_display::insert(Glyph_string text, std::size_t index) {
39 if (index > contents_.size()) {
40 return;
41 }
42 if (contents_.empty()) {
43 this->append(std::move(text));
44 return;
45 }
46 for (auto& glyph : text) {
47 for (Attribute a : Attribute_list) {
48 if (this->insert_brush.has_attribute(a)) {
49 glyph.brush.add_attributes(a);
50 }
51 }
52 }
53 contents_.insert(std::begin(contents_) + index, std::begin(text),
54 std::end(text));
55 this->update();
56 contents_modified(contents_);
57 }
58
append(Glyph_string text)59 void Text_display::append(Glyph_string text) {
60 for (auto& glyph : text) {
61 for (Attribute a : Attribute_list) {
62 if (this->insert_brush.has_attribute(a)) {
63 glyph.brush.add_attributes(a);
64 }
65 }
66 }
67 contents_.append(text);
68 this->update();
69 contents_modified(contents_);
70 }
71
erase(std::size_t index,std::size_t length)72 void Text_display::erase(std::size_t index, std::size_t length) {
73 if (contents_.empty() || index >= contents_.size()) {
74 return;
75 }
76 auto end = std::begin(contents_) + length + index;
77 if (length == Glyph_string::npos) {
78 end = std::end(contents_);
79 }
80 contents_.erase(std::begin(contents_) + index, end);
81 this->update();
82 contents_modified(contents_);
83 }
84
pop_back()85 void Text_display::pop_back() {
86 if (contents_.empty()) {
87 return;
88 }
89 contents_.pop_back();
90 this->update();
91 contents_modified(contents_);
92 }
93
clear()94 void Text_display::clear() {
95 contents_.clear();
96 this->cursor.set_x(0);
97 this->cursor.set_y(0);
98 this->update();
99 contents_modified(contents_);
100 }
101
set_alignment(Alignment type)102 void Text_display::set_alignment(Alignment type) {
103 alignment_ = type;
104 this->update();
105 }
106
scroll_up(std::size_t n)107 void Text_display::scroll_up(std::size_t n) {
108 if (n > this->top_line()) {
109 top_line_ = 0;
110 } else {
111 top_line_ -= n;
112 }
113 this->update();
114 scrolled_up(n);
115 }
116
scroll_down(std::size_t n)117 void Text_display::scroll_down(std::size_t n) {
118 if (this->top_line() + n > this->last_line()) {
119 top_line_ = this->last_line();
120 } else {
121 top_line_ += n;
122 }
123 this->update();
124 scrolled_down(n);
125 }
126
enable_word_wrap(bool enable)127 void Text_display::enable_word_wrap(bool enable) {
128 word_wrap_enabled_ = enable;
129 this->update();
130 }
131
disable_word_wrap(bool disable)132 void Text_display::disable_word_wrap(bool disable) {
133 word_wrap_enabled_ = !disable;
134 this->update();
135 }
136
toggle_word_wrap()137 void Text_display::toggle_word_wrap() {
138 word_wrap_enabled_ = !word_wrap_enabled_;
139 this->update();
140 }
141
row_length(std::size_t y) const142 std::size_t Text_display::row_length(std::size_t y) const {
143 const auto line = this->top_line() + y;
144 return this->line_length(line);
145 }
146
index_at(Point position) const147 std::size_t Text_display::index_at(Point position) const {
148 auto line = this->top_line() + position.y;
149 if (line >= display_state_.size()) {
150 return this->contents().size();
151 }
152 auto info = display_state_.at(line);
153 if (position.x >= info.length) {
154 if (info.length == 0) {
155 position.x = 0;
156 } else if (this->top_line() + position.y != this->last_line()) {
157 return this->first_index_at(this->top_line() + position.y + 1) - 1;
158 } else if (this->top_line() + position.y == this->last_line()) {
159 return this->contents().size();
160 } else {
161 position.x = info.length - 1;
162 }
163 }
164 return info.start_index + position.x;
165 }
166
display_position(std::size_t index) const167 Point Text_display::display_position(std::size_t index) const {
168 Point position;
169 auto line = this->line_at(index);
170 if (line < this->top_line()) {
171 return position;
172 }
173 const auto last_shown_line = this->bottom_line();
174 if (line > last_shown_line) {
175 line = last_shown_line;
176 index = this->last_index_at(line);
177 } else if (index > this->contents().size()) {
178 index = this->contents().size();
179 }
180 position.y = line - this->top_line();
181 position.x = index - this->first_index_at(line);
182 return position;
183 }
184
paint_event()185 bool Text_display::paint_event() {
186 Painter p{*this};
187 std::size_t line_n{0};
188 auto paint = [&p, &line_n, this](const Line_info& line) {
189 auto sub_begin = std::begin(this->contents_) + line.start_index;
190 auto sub_end = sub_begin + line.length;
191 std::size_t start{0};
192 switch (alignment_) {
193 case Alignment::Left:
194 start = 0;
195 break;
196 case Alignment::Center:
197 start = (this->width() - line.length) / 2;
198 break;
199 case Alignment::Right:
200 start = this->width() - line.length;
201 break;
202 }
203 p.put(Glyph_string(sub_begin, sub_end), start, line_n++);
204 };
205 auto begin = std::begin(display_state_) + this->top_line();
206 auto end = std::end(display_state_);
207 if (display_state_.size() > this->top_line() + this->height()) {
208 end = begin + this->height(); // maybe make this the initial value?
209 }
210 if (this->top_line() < display_state_.size()) {
211 std::for_each(begin, end, paint);
212 }
213 return Widget::paint_event();
214 }
215
216 // TODO: Implement tab character. and newline?
217 // if (glyph.symbol == L'\n') {
218 // move_cursor(*widget_, 0, widget_->cursor_y() + 1);
219 // return;
220 // } else if (glyph.symbol == L'\t') {
221 // // TODO move cursor to next x coord divisible by tablength
222 // // textbox should account for it.
223 // return;
224 // }
225
update_display(std::size_t from_line)226 void Text_display::update_display(std::size_t from_line) {
227 std::size_t begin = display_state_.at(from_line).start_index;
228 display_state_.clear();
229 if (this->width() == 0) {
230 display_state_.push_back(Line_info{0, 0});
231 return;
232 }
233 std::size_t start_index{0};
234 std::size_t length{0};
235 std::size_t last_space{0};
236 for (std::size_t i{begin}; i < contents_.size(); ++i) {
237 ++length;
238 if (this->word_wrap_enabled() && contents_.at(i).symbol == L' ') {
239 last_space = length;
240 }
241 if (contents_.at(i).symbol == L'\n') {
242 display_state_.push_back(Line_info{start_index, length - 1});
243 start_index += length;
244 length = 0;
245 } else if (length == this->width()) {
246 if (this->word_wrap_enabled() && last_space > 0) {
247 i -= length - last_space;
248 length = last_space;
249 last_space = 0;
250 }
251 display_state_.push_back(Line_info{start_index, length});
252 start_index += length;
253 length = 0;
254 }
255 }
256 display_state_.push_back(Line_info{start_index, length});
257 // Reset top_line_ if out of bounds of new display.
258 if (this->top_line() >= display_state_.size()) {
259 top_line_ = this->last_line();
260 }
261 }
262
line_at(std::size_t index) const263 std::size_t Text_display::line_at(std::size_t index) const {
264 std::size_t line{0};
265 // TODO Can you binary search this?
266 for (const auto& info : display_state_) {
267 if (info.start_index <= index) {
268 ++line;
269 } else {
270 return line - 1;
271 }
272 }
273 return this->last_line();
274 }
275
display_height() const276 std::size_t Text_display::display_height() const {
277 auto difference = 1 + this->last_line() - this->top_line();
278 if (difference > this->height()) {
279 difference = this->height();
280 }
281 return difference;
282 }
283
first_index_at(std::size_t line) const284 std::size_t Text_display::first_index_at(std::size_t line) const {
285 if (line >= display_state_.size()) {
286 line = display_state_.size() - 1;
287 }
288 return display_state_.at(line).start_index;
289 }
290
last_index_at(std::size_t line) const291 std::size_t Text_display::last_index_at(std::size_t line) const {
292 const auto next_line = line + 1;
293 if (next_line >= display_state_.size()) {
294 return this->end_index();
295 }
296 return display_state_.at(next_line).start_index;
297 }
298
line_length(std::size_t line) const299 std::size_t Text_display::line_length(std::size_t line) const {
300 if (line >= display_state_.size()) {
301 line = display_state_.size() - 1;
302 }
303 return display_state_.at(line).length;
304 }
305 } // namespace cppurses
306