1#include <iostream>
2#include <sstream>
3#include <fstream>
4
5#include "sdl_painter_demo.hpp"
6#include "simple_time.hpp"
7#include "PanZoomTracker.hpp"
8#include "read_path.hpp"
9#include "ImageLoader.hpp"
10#include "colorstop_command_line.hpp"
11#include "cycle_value.hpp"
12
13using namespace fastuidraw;
14
15class rounded_corner_radii
16{
17public:
18 rounded_corner_radii(const std::string &name,
19 command_line_register &parent):
20 m_x(10.0f, "rect_" + name + "_x", "Rounded rectangle " + name + "-radii-x", parent),
21 m_y(5.0f, "rect_" + name + "_y", "Rounded rectangle " + name + "-radii-y", parent)
22 {}
23
24 vec2
25 value(void) const
26 {
27 return vec2(m_x.value(), m_y.value());
28 }
29
30 command_line_argument_value<float> m_x, m_y;
31};
32
33#ifndef FASTUIDRAW_GL_USE_GLES
34
35class EnableWireFrameAction:public PainterDrawBreakAction
36{
37public:
38 explicit
39 EnableWireFrameAction(bool b):
40 m_lines(b)
41 {}
42
43 virtual
44 fastuidraw::gpu_dirty_state
45 execute(PainterBackend*) const
46 {
47 if (m_lines)
48 {
49 fastuidraw_glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
50 fastuidraw_glLineWidth(4.0);
51 }
52 else
53 {
54 fastuidraw_glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
55 }
56 return 0u;
57 }
58
59private:
60 bool m_lines;
61};
62
63#endif
64
65class painter_clip_test:public sdl_painter_demo
66{
67public:
68 painter_clip_test(void);
69
70protected:
71 void
72 derived_init(int, int);
73
74 void
75 draw_frame(void);
76
77 void
78 handle_event(const SDL_Event &ev);
79
80private:
81
82 enum
83 {
84 clip_in,
85 clip_out,
86 no_clip,
87
88 number_clip_modes
89 };
90
91 enum
92 {
93 view_transformer,
94 path1_transformer,
95 path2_transformer,
96 rect_transformer,
97
98 number_transformers
99 };
100
101 enum
102 {
103 separate_clipping,
104 path1_then_path2,
105 path2_then_path1,
106
107 number_combine_clip_modes
108 };
109
110 class Transformer
111 {
112 public:
113 Transformer(void):
114 m_shear(1.0f, 1.0f),
115 m_angle(0.0f)
116 {}
117
118 void
119 concat_to_painter(const reference_counted_ptr<Painter> &ref) const
120 {
121 const float conv(FASTUIDRAW_PI / 180.0f);
122 m_zoomer.transformation().concat_to_painter(ref);
123 ref->shear(m_shear.x(), m_shear.y());
124 ref->rotate(m_angle * conv);
125 }
126
127 PanZoomTrackerSDLEvent m_zoomer;
128 vec2 m_shear;
129 float m_angle;
130 };
131
132 void
133 draw_element(const Path &path, unsigned int clip_mode, const vec4 &color,
134 const Transformer &matrix);
135
136 void
137 draw_combined(const Path &path1, unsigned int clip_mode1, const Transformer &matrix1,
138 const Path &path2, unsigned int clip_mode2, const Transformer &matrix2,
139 const vec4 &color);
140
141 enum return_code
142 load_path(Path &out_path, const std::string &file);
143
144 void
145 make_paths(void);
146
147 void
148 update_cts_params(void);
149
150 command_line_argument_value<std::string> m_path1_file;
151 command_line_argument_value<std::string> m_path2_file;
152 command_line_argument_value<std::string> m_image_file;
153 command_line_argument_value<float> m_rect_width;
154 command_line_argument_value<float> m_rect_height;
155 rounded_corner_radii m_rect_minx_miny_radii;
156 rounded_corner_radii m_rect_minx_maxy_radii;
157 rounded_corner_radii m_rect_maxx_miny_radii;
158 rounded_corner_radii m_rect_maxx_maxy_radii;
159
160 Path m_path1, m_path2;
161 RoundedRect m_rect;
162 reference_counted_ptr<const Image> m_image;
163
164 unsigned int m_path1_clip_mode, m_path2_clip_mode;
165 unsigned int m_combine_clip_mode, m_rounded_rect_mode;
166 unsigned int m_active_transformer;
167 bool m_aa_mode;
168 bool m_show_wire_frame;
169 vecN<Transformer, number_transformers> m_transformers;
170 vecN<std::string, number_clip_modes> m_clip_labels;
171 vecN<std::string, number_transformers> m_transformer_labels;
172 vecN<std::string, number_combine_clip_modes> m_combine_clip_labels;
173 simple_time m_draw_timer;
174 int m_show_surface, m_last_shown_surface;
175};
176
177painter_clip_test::
178painter_clip_test():
179 m_path1_file("", "path1_file",
180 "if non-empty read the geometry of the path1 from the specified file, "
181 "otherwise use a default path",
182 *this),
183 m_path2_file("", "path2_file",
184 "if non-empty read the geometry of the path1 from the specified file, "
185 "otherwise use a default path",
186 *this),
187 m_image_file("", "image", "if a valid file name, apply an image to drawing the rounded rect", *this),
188 m_rect_width(100.0f, "rect_width", "Rounded rectangle width", *this),
189 m_rect_height(50.0f, "rect_height", "Rounded rectangle height", *this),
190 m_rect_minx_miny_radii("minx-miny", *this),
191 m_rect_minx_maxy_radii("minx-maxy", *this),
192 m_rect_maxx_miny_radii("maxx-miny", *this),
193 m_rect_maxx_maxy_radii("maxx-maxy", *this),
194 m_path1_clip_mode(no_clip),
195 m_path2_clip_mode(no_clip),
196 m_combine_clip_mode(separate_clipping),
197 m_rounded_rect_mode(no_clip),
198 m_active_transformer(view_transformer),
199 m_aa_mode(true),
200 m_show_wire_frame(false),
201 m_show_surface(0),
202 m_last_shown_surface(0)
203{
204 std::cout << "Controls:\n"
205 << "\t1: cycle through clip modes for path1\n"
206 << "\t2: cycle through clip modes for path2\n"
207 << "\ts: cycle through active transformer controls\n"
208 << "\tc: change combine clip mode\n"
209 << "\tr: change rounded rect mode\n"
210 << "\tu: change anti-alias mode\n"
211 << "\t6: x-shear (hold ctrl to decrease)\n"
212 << "\t7: y-shear (hold ctrl to decrease)\n"
213 << "\t0: Rotate left\n"
214 << "\t9: Rotate right\n";
215
216 #ifndef FASTUIDRAW_GL_USE_GLES
217 {
218 std::cout << "\tspace: toggle wire frame on rounded rect\n";
219 }
220 #endif
221
222 m_clip_labels[clip_in] = "clip_in";
223 m_clip_labels[clip_out] = "clip_out";
224 m_clip_labels[no_clip] = "no_clip";
225
226 m_transformer_labels[view_transformer] = "view_transformer";
227 m_transformer_labels[path1_transformer] = "path1_transformer";
228 m_transformer_labels[path2_transformer] = "path2_transformer";
229 m_transformer_labels[rect_transformer] = "rect_transformer";
230
231 m_combine_clip_labels[separate_clipping] = "separate_clipping";
232 m_combine_clip_labels[path1_then_path2] = "path1_then_path2";
233 m_combine_clip_labels[path2_then_path1] = "path2_then_path1";
234}
235
236void
237painter_clip_test::
238handle_event(const SDL_Event &ev)
239{
240 if (m_active_transformer != view_transformer)
241 {
242 /* invert y-direction for the other transformers */
243 ScaleTranslate<float> inv;
244
245 inv = m_transformers[view_transformer].m_zoomer.transformation().inverse();
246 m_transformers[m_active_transformer].m_zoomer.m_scale_event = vec2(inv.scale(), inv.scale());
247 m_transformers[m_active_transformer].m_zoomer.m_scale_zooming = inv.scale();
248 m_transformers[m_active_transformer].m_zoomer.m_translate_event = inv.translation();
249 }
250
251 m_transformers[m_active_transformer].m_zoomer.handle_event(ev);
252 switch(ev.type)
253 {
254 case SDL_WINDOWEVENT:
255 if (ev.window.event == SDL_WINDOWEVENT_RESIZED)
256 {
257 on_resize(ev.window.data1, ev.window.data2);
258 }
259 break;
260
261 case SDL_QUIT:
262 end_demo(0);
263 break;
264
265 case SDL_KEYUP:
266 switch(ev.key.keysym.sym)
267 {
268 case SDLK_ESCAPE:
269 end_demo(0);
270 break;
271 case SDLK_1:
272 cycle_value(m_path1_clip_mode, ev.key.keysym.mod & (KMOD_SHIFT|KMOD_CTRL|KMOD_ALT), number_clip_modes);
273 std::cout << "Path1 clip mode set to: " << m_clip_labels[m_path1_clip_mode] << "\n";
274 break;
275 case SDLK_2:
276 cycle_value(m_path2_clip_mode, ev.key.keysym.mod & (KMOD_SHIFT|KMOD_CTRL|KMOD_ALT), number_clip_modes);
277 std::cout << "Path2 clip mode set to: " << m_clip_labels[m_path2_clip_mode] << "\n";
278 break;
279 case SDLK_s:
280 cycle_value(m_active_transformer, ev.key.keysym.mod & (KMOD_SHIFT|KMOD_CTRL|KMOD_ALT), number_transformers);
281 std::cout << "Active zoomer set to: " << m_transformer_labels[m_active_transformer] << "\n";
282 break;
283 case SDLK_c:
284 cycle_value(m_combine_clip_mode, ev.key.keysym.mod & (KMOD_SHIFT|KMOD_CTRL|KMOD_ALT), number_combine_clip_modes);
285 std::cout << "Combine clip mode set to: " << m_combine_clip_labels[m_combine_clip_mode] << "\n";
286 break;
287 case SDLK_r:
288 cycle_value(m_rounded_rect_mode, ev.key.keysym.mod & (KMOD_SHIFT|KMOD_CTRL|KMOD_ALT), number_clip_modes);
289 std::cout << "Rounded rect mode set to: " << m_clip_labels[m_rounded_rect_mode] << "\n";
290 break;
291 case SDLK_u:
292 m_aa_mode = !m_aa_mode;
293 std::cout << "RoundedRect drawing anti-alias mode set to: " << m_aa_mode << "\n";
294 break;
295 case SDLK_o:
296 if (ev.key.keysym.mod & (KMOD_SHIFT | KMOD_ALT))
297 {
298 if (m_show_surface > 0)
299 {
300 --m_show_surface;
301 }
302 }
303 else
304 {
305 ++m_show_surface;
306 }
307 break;
308 case SDLK_SPACE:
309 m_show_wire_frame = !m_show_wire_frame;
310 break;
311 }
312 break;
313 };
314}
315
316void
317painter_clip_test::
318update_cts_params(void)
319{
320 const Uint8 *keyboard_state = SDL_GetKeyboardState(nullptr);
321 FASTUIDRAWassert(keyboard_state != nullptr);
322
323 float speed = static_cast<float>(m_draw_timer.restart_us()), speed_shear;
324 if (m_active_transformer == view_transformer)
325 {
326 return;
327 }
328
329 speed /= 1000.0f;
330 if (keyboard_state[SDL_SCANCODE_LSHIFT])
331 {
332 speed *= 0.1f;
333 }
334 if (keyboard_state[SDL_SCANCODE_RSHIFT])
335 {
336 speed *= 10.0f;
337 }
338
339 speed_shear = 0.01f * speed;
340 if (keyboard_state[SDL_SCANCODE_LCTRL] || keyboard_state[SDL_SCANCODE_RCTRL])
341 {
342 speed_shear = -speed_shear;
343 }
344
345 speed_shear = 0.01f * speed;
346 if (keyboard_state[SDL_SCANCODE_LCTRL] || keyboard_state[SDL_SCANCODE_RCTRL])
347 {
348 speed_shear = -speed_shear;
349 }
350
351 vec2 *pshear(&m_transformers[m_active_transformer].m_shear);
352 float *pangle(&m_transformers[m_active_transformer].m_angle);
353
354 if (keyboard_state[SDL_SCANCODE_6])
355 {
356 pshear->x() += speed_shear;
357 std::cout << "Shear set to: " << *pshear << "\n";
358 }
359 if (keyboard_state[SDL_SCANCODE_7])
360 {
361 pshear->y() += speed_shear;
362 std::cout << "Shear set to: " << *pshear << "\n";
363 }
364
365 if (keyboard_state[SDL_SCANCODE_9])
366 {
367 *pangle += speed * 0.1f;
368 std::cout << "Angle set to: " << *pangle << "\n";
369 }
370 if (keyboard_state[SDL_SCANCODE_0])
371 {
372 *pangle -= speed * 0.1f;
373 std::cout << "Angle set to: " << *pangle << "\n";
374 }
375}
376
377enum return_code
378painter_clip_test::
379load_path(Path &out_path, const std::string &filename)
380{
381 if (!filename.empty())
382 {
383 std::ifstream path_file(filename.c_str());
384 if (path_file)
385 {
386 std::stringstream buffer;
387 buffer << path_file.rdbuf();
388 read_path(out_path, buffer.str());
389 return routine_success;
390 }
391 }
392 return routine_fail;
393}
394
395
396void
397painter_clip_test::
398make_paths(void)
399{
400 if (routine_fail == load_path(m_path1, m_path1_file.value()))
401 {
402 m_path1 << vec2(100.0f, 100.0f)
403 << vec2(0.0f, 0.0f)
404 << vec2(100.0f, 0.0f)
405 << Path::contour_close();
406 }
407
408 if (routine_fail == load_path(m_path2, m_path2_file.value()))
409 {
410 m_path2 << vec2(100.0f, 0.0f)
411 << vec2(0.0f, 100.0f)
412 << vec2(100.0f, 100.0f)
413 << Path::contour_close();
414 }
415}
416
417void
418painter_clip_test::
419derived_init(int, int)
420{
421 make_paths();
422
423 if (!m_image_file.value().empty())
424 {
425 ImageLoader image_data(m_image_file.value());
426 m_image = m_painter->image_atlas().create(image_data.width(),
427 image_data.height(),
428 image_data,
429 Image::bindless_texture2d);
430 if (m_image)
431 {
432 std::cout << "Loaded image \"" << m_image_file.value()
433 << "\", dimensions = " << m_image->dimensions()
434 << "\n";
435 }
436 }
437
438 m_rect.m_min_point = vec2(0.0f, 0.0f);
439 m_rect.m_max_point = vec2(m_rect_width.value(), m_rect_height.value());
440 m_rect.m_corner_radii[Rect::minx_miny_corner] = m_rect_minx_miny_radii.value();
441 m_rect.m_corner_radii[Rect::minx_maxy_corner] = m_rect_minx_maxy_radii.value();
442 m_rect.m_corner_radii[Rect::maxx_miny_corner] = m_rect_maxx_miny_radii.value();
443 m_rect.m_corner_radii[Rect::maxx_maxy_corner] = m_rect_maxx_maxy_radii.value();
444 m_draw_timer.restart();
445}
446
447void
448painter_clip_test::
449draw_element(const Path &path, unsigned int clip_mode, const vec4 &color,
450 const Transformer &matrix)
451{
452 PainterBrush brush;
453
454 m_painter->save();
455 matrix.concat_to_painter(m_painter);
456 brush.color(color);
457 switch(clip_mode)
458 {
459 default:
460 break;
461
462 case clip_in:
463 m_painter->clip_in_path(path, Painter::nonzero_fill_rule);
464 break;
465 case clip_out:
466 m_painter->clip_out_path(path, Painter::nonzero_fill_rule);
467 break;
468 }
469
470 m_painter->fill_rect(PainterData(&brush), path.tessellation().bounding_box());
471 m_painter->restore();
472}
473
474void
475painter_clip_test::
476draw_combined(const Path &path1, unsigned int clip_mode1, const Transformer &matrix1,
477 const Path &path2, unsigned int clip_mode2, const Transformer &matrix2,
478 const vec4 &color)
479{
480 float3x3 M(m_painter->transformation());
481 PainterBrush brush;
482
483 brush.color(color);
484 m_painter->save();
485 matrix1.concat_to_painter(m_painter);
486 switch (clip_mode1)
487 {
488 default:
489 break;
490 case clip_in:
491 m_painter->clip_in_path(path1, Painter::nonzero_fill_rule);
492 break;
493 case clip_out:
494 m_painter->clip_out_path(path1, Painter::nonzero_fill_rule);
495 break;
496 }
497
498 m_painter->transformation(M);
499 matrix2.concat_to_painter(m_painter);
500 switch (clip_mode2)
501 {
502 default:
503 break;
504 case clip_in:
505 m_painter->clip_in_path(path2, Painter::nonzero_fill_rule);
506 break;
507 case clip_out:
508 m_painter->clip_out_path(path2, Painter::nonzero_fill_rule);
509 break;
510 }
511
512 m_painter->transformation(M);
513 matrix1.concat_to_painter(m_painter);
514
515 m_painter->fill_rect(PainterData(&brush), path1.tessellation().bounding_box());
516 m_painter->restore();
517}
518
519void
520painter_clip_test::
521draw_frame(void)
522{
523 update_cts_params();
524 m_painter->begin(m_surface, Painter::y_increases_downwards);
525 m_transformers[view_transformer].concat_to_painter(m_painter);
526
527 float3x3 M(m_painter->transformation());
528 m_transformers[rect_transformer].concat_to_painter(m_painter);
529 switch(m_rounded_rect_mode)
530 {
531 case no_clip:
532 {
533 PainterBrush brush;
534
535 if (m_image)
536 {
537 brush.image(m_image);
538 }
539 else
540 {
541 brush.color(vec4(1.0f, 1.0f, 0.0f, 1.0f));
542 }
543 m_painter->fill_rounded_rect(m_painter->default_shaders().fill_shader(),
544 PainterData(&brush), m_rect, m_aa_mode);
545
546 #ifndef FASTUIDRAW_GL_USE_GLES
547 {
548 if (m_show_wire_frame)
549 {
550 PainterBrush red;
551
552 red.color(1.0f, 0.0f, 0.0f, 0.25f);
553 m_painter->queue_action(FASTUIDRAWnew EnableWireFrameAction(true));
554 m_painter->fill_rounded_rect(m_painter->default_shaders().fill_shader(),
555 PainterData(&red), m_rect, false);
556 m_painter->queue_action(FASTUIDRAWnew EnableWireFrameAction(false));
557 }
558 }
559 #endif
560 }
561 break;
562
563 case clip_in:
564 m_painter->clip_in_rounded_rect(m_rect);
565 break;
566
567 case clip_out:
568 m_painter->clip_out_rounded_rect(m_rect);
569 break;
570
571 }
572 m_painter->transformation(M);
573
574 switch(m_combine_clip_mode)
575 {
576 case separate_clipping:
577 draw_element(m_path1, m_path1_clip_mode, vec4(1.0f, 0.0f, 0.0f, 0.5f),
578 m_transformers[path1_transformer]);
579 draw_element(m_path2, m_path2_clip_mode, vec4(0.0f, 1.0f, 0.0f, 0.5f),
580 m_transformers[path2_transformer]);
581 break;
582
583 case path1_then_path2:
584 draw_combined(m_path1, m_path1_clip_mode, m_transformers[path1_transformer],
585 m_path2, m_path2_clip_mode, m_transformers[path2_transformer],
586 vec4(0.0f, 1.0f, 1.0f, 0.5f));
587 break;
588
589 case path2_then_path1:
590 draw_combined(m_path2, m_path2_clip_mode, m_transformers[path2_transformer],
591 m_path1, m_path1_clip_mode, m_transformers[path1_transformer],
592 vec4(1.0f, 0.0f, 1.0f, 0.5f));
593 break;
594 }
595
596 c_array<const PainterSurface* const> surfaces;
597
598 surfaces = m_painter->end();
599 fastuidraw_glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
600 fastuidraw_glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
601
602 m_show_surface = t_min(m_show_surface, (int)surfaces.size());
603 if (m_show_surface <= 0 || m_show_surface > surfaces.size())
604 {
605 m_surface->blit_surface(GL_NEAREST);
606 }
607 else
608 {
609 const gl::PainterSurfaceGL *S;
610 PainterSurface::Viewport src, dest;
611
612 src = m_surface->viewport();
613 S = dynamic_cast<const gl::PainterSurfaceGL*>(surfaces[m_show_surface - 1]);
614
615 dest.m_origin = src.m_origin;
616 dest.m_dimensions = ivec2(src.m_dimensions.x(), src.m_dimensions.y() / 2);
617 m_surface->blit_surface(src, dest, GL_LINEAR);
618
619 dest.m_origin.y() += dest.m_dimensions.y();
620 S->blit_surface(src, dest, GL_LINEAR);
621 }
622
623 if (m_last_shown_surface != m_show_surface)
624 {
625 if (m_show_surface > 0)
626 {
627 std::cout << "Show offscreen surface: " << m_show_surface - 1 << "\n";
628 }
629 else
630 {
631 std::cout << "Don't show offscreen surface\n";
632 }
633 m_last_shown_surface = m_show_surface;
634 }
635}
636
637int
638main(int argc, char **argv)
639{
640 painter_clip_test P;
641 return P.main(argc, argv);
642}
643