1 #include "paint_area.hpp"
2 
3 #include <cctype>
4 #include <codecvt>
5 #include <cstddef>
6 #include <cstdint>
7 #include <iostream>
8 #include <iterator>
9 #include <locale>
10 #include <string>
11 #include <utility>
12 
13 #include <cppurses/painter/attribute.hpp>
14 #include <cppurses/painter/brush.hpp>
15 #include <cppurses/painter/color.hpp>
16 #include <cppurses/painter/glyph.hpp>
17 #include <cppurses/painter/glyph_string.hpp>
18 #include <cppurses/painter/painter.hpp>
19 #include <cppurses/system/events/key.hpp>
20 #include <cppurses/system/events/mouse.hpp>
21 #include <cppurses/widget/border.hpp>
22 #include <cppurses/widget/focus_policy.hpp>
23 #include <cppurses/widget/point.hpp>
24 #include <cppurses/widget/widget.hpp>
25 
26 #include <optional/optional.hpp>
27 #include <signals/slot.hpp>
28 
29 using namespace cppurses;
30 
31 namespace {
32 
insert_newline(Point first,Point second,std::ostream & os)33 void insert_newline(Point first, Point second, std::ostream& os) {
34     if (first.y >= second.y) {
35         return;
36     }
37     std::string newlines(second.y - first.y, '\n');
38     os << newlines;
39 }
40 
insert_space(Point first,Point second,std::ostream & os)41 void insert_space(Point first, Point second, std::ostream& os) {
42     std::size_t spaces_n{second.x};
43     if (first.y == second.y) {
44         spaces_n -= first.x + 1;
45     }
46     std::string spaces(spaces_n, ' ');
47     os << spaces;
48 }
49 
50 }  // namespace
51 
52 namespace demos {
53 namespace glyph_paint {
54 
Paint_area()55 Paint_area::Paint_area() {
56     this->focus_policy = Focus_policy::Strong;
57 
58     this->border.enable();
59     this->border.segments.disable_all();
60     this->border.segments.east.enable();
61 }
62 
set_glyph(Glyph glyph)63 void Paint_area::set_glyph(Glyph glyph) {
64     current_glyph_ = std::move(glyph);
65     glyph_changed(current_glyph_);
66     if (erase_enabled_) {
67         this->disable_erase();
68         erase_disabled();
69     }
70 }
71 
set_symbol(const Glyph & sym)72 void Paint_area::set_symbol(const Glyph& sym) {
73     if (erase_enabled_) {
74         this->disable_erase();
75         erase_disabled();
76     }
77     current_glyph_.symbol = sym.symbol;
78     opt::Optional<Color> sym_bg{sym.brush.background_color()};
79     if (sym_bg) {
80         current_glyph_.brush.set_background(*sym_bg);
81     }
82     opt::Optional<Color> sym_fg{sym.brush.foreground_color()};
83     if (sym_fg) {
84         current_glyph_.brush.set_foreground(*sym_fg);
85     }
86     glyph_changed(current_glyph_);
87 }
88 
set_foreground_color(Color c)89 void Paint_area::set_foreground_color(Color c) {
90     current_glyph_.brush.set_foreground(c);
91     if (!erase_enabled_) {
92         glyph_changed(current_glyph_);
93     }
94 }
95 
set_background_color(Color c)96 void Paint_area::set_background_color(Color c) {
97     current_glyph_.brush.set_background(c);
98     if (!erase_enabled_) {
99         glyph_changed(current_glyph_);
100     }
101 }
102 
set_attribute(Attribute attr)103 void Paint_area::set_attribute(Attribute attr) {
104     current_glyph_.brush.add_attributes(attr);
105     if (!erase_enabled_) {
106         glyph_changed(current_glyph_);
107     }
108 }
109 
remove_attribute(Attribute attr)110 void Paint_area::remove_attribute(Attribute attr) {
111     current_glyph_.brush.remove_attributes(attr);
112     if (!erase_enabled_) {
113         glyph_changed(current_glyph_);
114     }
115 }
116 
enable_erase()117 void Paint_area::enable_erase() {
118     erase_enabled_ = true;
119     glyph_changed(L' ');
120 }
121 
disable_erase()122 void Paint_area::disable_erase() {
123     erase_enabled_ = false;
124     glyph_changed(current_glyph_);
125 }
126 
enable_grid()127 void Paint_area::enable_grid() {
128     this->wallpaper = Glyph{L'┼', foreground(Color::Dark_gray)};
129     this->update();
130 }
131 
disable_grid()132 void Paint_area::disable_grid() {
133     this->wallpaper = L' ';
134     this->update();
135 }
136 
clear()137 void Paint_area::clear() {
138     glyphs_painted_.clear();
139     this->update();
140 }
141 
glyph() const142 Glyph Paint_area::glyph() const {
143     return current_glyph_;
144 }
145 
toggle_clone()146 void Paint_area::toggle_clone() {
147     clone_enabled_ = !clone_enabled_;
148 }
149 
write(std::ostream & os)150 void Paint_area::write(std::ostream& os) {
151     Point previous_nl{0, 0};
152     Point previous_s{0, static_cast<std::size_t>(-1)};
153     for (const auto& cg_pair : glyphs_painted_) {
154         insert_newline(previous_nl, cg_pair.first, os);
155         insert_space(previous_s, cg_pair.first, os);
156         os << cppurses::utility::wchar_to_bytes(cg_pair.second.symbol);
157         previous_nl = cg_pair.first;
158         previous_s = cg_pair.first;
159     }
160 }
161 
read(std::istream & is)162 void Paint_area::read(std::istream& is) {
163     this->clear();
164     Point current{0, 0};
165     is >> std::noskipws;
166     std::string file_text{std::istream_iterator<char>{is},
167                           std::istream_iterator<char>()};
168     Glyph_string file_glyphs{file_text};
169     for (const Glyph& glyph : file_glyphs) {
170         const wchar_t sym{glyph.symbol};
171         if (sym != L' ' && sym != L'\n' && sym != L'\r') {
172             glyphs_painted_[current] = glyph;
173         }
174         ++current.x;
175         if (sym == L'\n') {
176             ++current.y;
177             current.x = 0;
178         }
179     }
180 }
181 
paint_event()182 bool Paint_area::paint_event() {
183     Painter p{*this};
184     for (const auto& gc_pair : glyphs_painted_) {
185         if (gc_pair.first.x < this->width() &&
186             gc_pair.first.y < this->height()) {
187             p.put(gc_pair.second, gc_pair.first);
188         }
189     }
190     return Widget::paint_event();
191 }
192 
mouse_press_event(const Mouse::State & mouse)193 bool Paint_area::mouse_press_event(const Mouse::State& mouse) {
194     if (mouse.button == Mouse::Button::Right) {
195         this->remove_glyph(mouse.local);
196     } else if (mouse.button == Mouse::Button::Middle) {
197         if (glyphs_painted_.count(mouse.local) == 1) {
198             this->set_glyph(glyphs_painted_[mouse.local]);
199         }
200     } else {
201         this->place_glyph(mouse.local.x, mouse.local.y);
202     }
203     return Widget::mouse_press_event(mouse);
204 }
205 
key_press_event(const Key::State & keyboard)206 bool Paint_area::key_press_event(const Key::State& keyboard) {
207     if (!this->cursor.enabled()) {
208         if (!std::iscntrl(keyboard.symbol)) {
209             this->set_symbol(keyboard.symbol);
210         }
211         return Widget::key_press_event(keyboard);
212     }
213     if (this->width() == 0 || this->height() == 0) {
214         return Widget::key_press_event(keyboard);
215     }
216     std::size_t new_x{this->cursor.x() + 1};
217     std::size_t new_y{this->cursor.y() + 1};
218     switch (keyboard.key) {
219         case Key::Arrow_right:
220             if (new_x == this->width()) {
221                 new_x = 0;
222             }
223             this->cursor.set_x(new_x);
224             break;
225         case Key::Arrow_left:
226             this->cursor.set_x(this->cursor.x() - 1);
227             break;
228         case Key::Arrow_down:
229             if (new_y == this->height()) {
230                 new_y = 0;
231             }
232             this->cursor.set_y(new_y);
233             break;
234         case Key::Arrow_up:
235             this->cursor.set_y(this->cursor.y() - 1);
236             break;
237         case Key::Enter:
238             this->place_glyph(this->cursor.x(), this->cursor.y());
239             break;
240         default:
241             if (!std::iscntrl(keyboard.symbol)) {
242                 this->set_symbol(keyboard.symbol);
243                 this->place_glyph(this->cursor.x(), this->cursor.y());
244                 this->update();
245             }
246             break;
247     }
248     return Widget::key_press_event(keyboard);
249 }
250 
place_glyph(std::size_t x,std::size_t y)251 void Paint_area::place_glyph(std::size_t x, std::size_t y) {
252     if (clone_enabled_) {
253         if (glyphs_painted_.count(Point{x, y}) == 1) {
254             this->set_glyph(glyphs_painted_[Point{x, y}]);
255             this->toggle_clone();
256         }
257     } else if (erase_enabled_) {
258         this->remove_glyph(Point{x, y});
259     } else {
260         glyphs_painted_[Point{x, y}] = current_glyph_;
261         this->update();
262     }
263 }
264 
remove_glyph(Point coords)265 void Paint_area::remove_glyph(Point coords) {
266     glyphs_painted_.erase(coords);
267     this->update();
268 }
269 
270 namespace slot {
271 
set_glyph(Paint_area & pa)272 sig::Slot<void(Glyph)> set_glyph(Paint_area& pa) {
273     sig::Slot<void(Glyph)> slot{[&pa](Glyph g) { pa.set_glyph(std::move(g)); }};
274     slot.track(pa.destroyed);
275     return slot;
276 }
277 
set_glyph(Paint_area & pa,const Glyph & glyph)278 sig::Slot<void()> set_glyph(Paint_area& pa, const Glyph& glyph) {
279     sig::Slot<void()> slot{[&pa, glyph] { pa.set_glyph(glyph); }};
280     slot.track(pa.destroyed);
281     return slot;
282 }
283 
set_symbol(Paint_area & pa)284 sig::Slot<void(Glyph)> set_symbol(Paint_area& pa) {
285     sig::Slot<void(Glyph)> slot{
286         [&pa](Glyph symbol) { pa.set_symbol(std::move(symbol)); }};
287     slot.track(pa.destroyed);
288     return slot;
289 }
290 
set_symbol(Paint_area & pa,const Glyph & symbol)291 sig::Slot<void()> set_symbol(Paint_area& pa, const Glyph& symbol) {
292     sig::Slot<void()> slot{[&pa, symbol] { pa.set_symbol(symbol); }};
293     slot.track(pa.destroyed);
294     return slot;
295 }
296 
set_foreground_color(Paint_area & pa)297 sig::Slot<void(Color)> set_foreground_color(Paint_area& pa) {
298     sig::Slot<void(Color)> slot{[&pa](Color c) { pa.set_foreground_color(c); }};
299     slot.track(pa.destroyed);
300     return slot;
301 }
302 
set_foreground_color(Paint_area & pa,Color c)303 sig::Slot<void()> set_foreground_color(Paint_area& pa, Color c) {
304     sig::Slot<void()> slot{[&pa, c] { pa.set_foreground_color(c); }};
305     slot.track(pa.destroyed);
306     return slot;
307 }
308 
set_background_color(Paint_area & pa)309 sig::Slot<void(Color)> set_background_color(Paint_area& pa) {
310     sig::Slot<void(Color)> slot{[&pa](Color c) { pa.set_background_color(c); }};
311     slot.track(pa.destroyed);
312     return slot;
313 }
314 
set_background_color(Paint_area & pa,Color c)315 sig::Slot<void()> set_background_color(Paint_area& pa, Color c) {
316     sig::Slot<void()> slot{[&pa, c] { pa.set_background_color(c); }};
317     slot.track(pa.destroyed);
318     return slot;
319 }
320 
set_attribute(Paint_area & pa)321 sig::Slot<void(Attribute)> set_attribute(Paint_area& pa) {
322     sig::Slot<void(Attribute)> slot{
323         [&pa](Attribute attr) { pa.set_attribute(attr); }};
324     slot.track(pa.destroyed);
325     return slot;
326 }
327 
set_attribute(Paint_area & pa,Attribute attr)328 sig::Slot<void()> set_attribute(Paint_area& pa, Attribute attr) {
329     sig::Slot<void()> slot{[&pa, attr] { pa.set_attribute(attr); }};
330     slot.track(pa.destroyed);
331     return slot;
332 }
333 
remove_attribute(Paint_area & pa)334 sig::Slot<void(Attribute)> remove_attribute(Paint_area& pa) {
335     sig::Slot<void(Attribute)> slot{
336         [&pa](Attribute attr) { pa.remove_attribute(attr); }};
337     slot.track(pa.destroyed);
338     return slot;
339 }
340 
remove_attribute(Paint_area & pa,Attribute attr)341 sig::Slot<void()> remove_attribute(Paint_area& pa, Attribute attr) {
342     sig::Slot<void()> slot{[&pa, attr] { pa.remove_attribute(attr); }};
343     slot.track(pa.destroyed);
344     return slot;
345 }
346 
toggle_clone(Paint_area & pa)347 sig::Slot<void()> toggle_clone(Paint_area& pa) {
348     sig::Slot<void()> slot{[&pa] { pa.toggle_clone(); }};
349     slot.track(pa.destroyed);
350     return slot;
351 }
352 
clear(Paint_area & pa)353 sig::Slot<void()> clear(Paint_area& pa) {
354     sig::Slot<void()> slot{[&pa] { pa.clear(); }};
355     slot.track(pa.destroyed);
356     return slot;
357 }
358 
enable_erase(Paint_area & pa)359 sig::Slot<void()> enable_erase(Paint_area& pa) {
360     sig::Slot<void()> slot{[&pa] { pa.enable_erase(); }};
361     slot.track(pa.destroyed);
362     return slot;
363 }
364 
disable_erase(Paint_area & pa)365 sig::Slot<void()> disable_erase(Paint_area& pa) {
366     sig::Slot<void()> slot{[&pa] { pa.disable_erase(); }};
367     slot.track(pa.destroyed);
368     return slot;
369 }
370 
enable_grid(Paint_area & pa)371 sig::Slot<void()> enable_grid(Paint_area& pa) {
372     sig::Slot<void()> slot{[&pa] { pa.enable_grid(); }};
373     slot.track(pa.destroyed);
374     return slot;
375 }
376 
disable_grid(Paint_area & pa)377 sig::Slot<void()> disable_grid(Paint_area& pa) {
378     sig::Slot<void()> slot{[&pa] { pa.disable_grid(); }};
379     slot.track(pa.destroyed);
380     return slot;
381 }
382 
write(Paint_area & pa)383 sig::Slot<void(std::ostream&)> write(Paint_area& pa) {
384     sig::Slot<void(std::ostream&)> slot{
385         [&pa](std::ostream& os) { pa.write(os); }};
386     slot.track(pa.destroyed);
387     return slot;
388 }
389 
read(Paint_area & pa)390 sig::Slot<void(std::istream&)> read(Paint_area& pa) {
391     sig::Slot<void(std::istream&)> slot{
392         [&pa](std::istream& is) { pa.read(is); }};
393     slot.track(pa.destroyed);
394     return slot;
395 }
396 
397 }  // namespace slot
398 }  // namespace glyph_paint
399 }  // namespace demos
400