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