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