1#pragma once
2
3#include "taichi/math/math.h"
4#include "taichi/system/timer.h"
5#include "taichi/program/kernel_profiler.h"
6
7#include <atomic>
8#include <ctime>
9#include <numeric>
10#include <unordered_map>
11
12#if defined(TI_PLATFORM_LINUX) || \
13 (defined(TI_PLATFORM_UNIX) && !defined(TI_PLATFORM_OSX))
14#if defined(TI_PLATFORM_ANDROID)
15#define TI_GUI_ANDROID
16#else
17#define TI_GUI_X11
18#endif
19#endif
20
21#if defined(TI_PLATFORM_WINDOWS)
22#define TI_GUI_WIN32
23#endif
24
25#if defined(TI_PLATFORM_OSX)
26#define TI_GUI_COCOA
27#include <objc/objc.h>
28#endif
29
30namespace taichi {
31
32TI_FORCE_INLINE Vector4 color_from_hex(uint32 c) {
33 return Vector4(c / 65536, c / 256 % 256, c % 256, 255) * (1 / 255.0_f);
34}
35
36#if (false)
37constexpr uint32 text_color = 0x02547D;
38constexpr uint32 widget_bg = 0x02BEC4;
39constexpr uint32 widget_hover = 0xA9E8DC;
40constexpr uint32 slider_bar_color = text_color;
41constexpr uint32 slider_circle_color = 0x0284A8;
42#else
43constexpr uint32 text_color = 0x111111;
44constexpr uint32 widget_bg = 0xAAAAAA;
45constexpr uint32 widget_hover = 0xCCCCCC;
46constexpr uint32 slider_bar_color = 0x333333;
47constexpr uint32 slider_circle_color = 0x555555;
48#endif
49
50class TI_DLL_EXPORT Canvas {
51 struct Context {
52 Vector4 _color;
53 real _radius;
54 };
55
56 public:
57 Context context;
58
59 Canvas &color(Vector4 val) {
60 context._color = val;
61 return *this;
62 }
63
64 TI_FORCE_INLINE Canvas &color(real r, real g, real b, real a = 1) {
65 context._color = Vector4(r, g, b, a);
66 return *this;
67 }
68
69 TI_FORCE_INLINE Canvas &color(int r, int g, int b, int a = 255) {
70 context._color = (1.0_f / 255) * Vector4(r, g, b, a);
71 return *this;
72 }
73
74 TI_FORCE_INLINE Canvas &radius(real radius) {
75 context._radius = radius;
76 return *this;
77 }
78
79 struct Line {
80 Canvas &canvas;
81 Vector4 _color;
82 real _radius;
83 int n_vertices;
84 bool finished;
85 static Vector2 vertices[128]; // TODO: ...
86
87 TI_FORCE_INLINE explicit Line(Canvas &canvas)
88 : canvas(canvas),
89 _color(canvas.context._color),
90 _radius(canvas.context._radius) {
91 n_vertices = 0;
92 finished = false;
93 }
94
95 TI_FORCE_INLINE Line(Canvas &canvas, Vector2 a, Vector2 b) : Line(canvas) {
96 push(a);
97 push(b);
98 }
99
100 TI_FORCE_INLINE Line(Canvas &canvas, Vector2 a, Vector2 b, Vector2 c)
101 : Line(canvas) {
102 push(a);
103 push(b);
104 push(c);
105 }
106
107 TI_FORCE_INLINE Line(Canvas &canvas,
108 Vector2 a,
109 Vector2 b,
110 Vector2 c,
111 Vector2 d)
112 : Line(canvas) {
113 push(a);
114 push(b);
115 push(c);
116 push(d);
117 }
118
119 TI_FORCE_INLINE void push(Vector2 vec) {
120 vertices[n_vertices++] = vec;
121 }
122
123 TI_FORCE_INLINE Line &path(Vector2 a) {
124 push(a);
125 return *this;
126 }
127
128 TI_FORCE_INLINE Line &path(Vector2 a, Vector2 b) {
129 push(a);
130 push(b);
131 return *this;
132 }
133
134 TI_FORCE_INLINE Line &path(Vector2 a, Vector2 b, Vector2 c) {
135 push(a);
136 push(b);
137 push(c);
138 return *this;
139 }
140
141 TI_FORCE_INLINE Line &path(Vector2 a, Vector2 b, Vector2 c, Vector2 d) {
142 push(a);
143 push(b);
144 push(c);
145 push(d);
146 return *this;
147 }
148
149 TI_FORCE_INLINE Line &close() {
150 TI_ASSERT(n_vertices > 0);
151 push(vertices[0]);
152 return *this;
153 }
154
155 TI_FORCE_INLINE Line &color(Vector4 color) {
156 _color = color;
157 return *this;
158 }
159
160 TI_FORCE_INLINE Line &color(int c) {
161 return color(c / 65536, c / 256 % 256, c % 256, 255);
162 }
163
164 TI_FORCE_INLINE Line &color(real r, real g, real b, real a = 1) {
165 _color = Vector4(r, g, b, a);
166 return *this;
167 }
168
169 TI_FORCE_INLINE Line &color(int r, int g, int b, int a = 255) {
170 _color = (1.0_f / 255) * Vector4(r, g, b, a);
171 return *this;
172 }
173
174 TI_FORCE_INLINE Line &width(real width) {
175 _radius = width * 0.5;
176 return *this;
177 }
178
179 TI_FORCE_INLINE Line &radius(real radius) {
180 _radius = radius;
181 return *this;
182 }
183
184 // TODO: end style e.g. arrow
185
186 void stroke(Vector2 a, Vector2 b) {
187 // TODO: accelerate
188 auto a_i = (a + Vector2(0.5_f)).template cast<int>();
189 auto b_i = (b + Vector2(0.5_f)).template cast<int>();
190 auto radius_i = (int)std::ceil(_radius + 0.5_f);
191 auto range_lower = Vector2i(std::min(a_i.x, b_i.x) - radius_i,
192 std::min(a_i.y, b_i.y) - radius_i);
193 range_lower(0) = std::max(0, range_lower(0));
194 range_lower(1) = std::max(0, range_lower(1));
195 auto range_higher = Vector2i(std::max(a_i.x, b_i.x) + radius_i,
196 std::max(a_i.y, b_i.y) + radius_i);
197 range_higher(0) = std::min(canvas.img.get_width() - 1, range_higher(0));
198 range_higher(1) = std::min(canvas.img.get_height() - 1, range_higher(1));
199 auto direction = normalized(b - a);
200 auto l = length(b - a);
201 auto tangent = Vector2(-direction.y, direction.x);
202 for (int i = range_lower.x; i <= range_higher.x; i++) {
203 for (int j = range_lower.y; j <= range_higher.y; j++) {
204 auto pixel_coord = Vector2(i + 0.5_f, j + 0.5_f) - a;
205 auto u = dot(tangent, pixel_coord);
206 auto v = dot(direction, pixel_coord);
207 if (v > 0) {
208 v = std::max(0.0_f, v - l);
209 }
210 real dist = length(Vector2(u, v));
211 auto alpha = _color.w * clamp(_radius - dist);
212 auto &dest = canvas.img[Vector2i(i, j)];
213 dest = lerp(alpha, dest, _color);
214 }
215 }
216 }
217
218 void finish() {
219 TI_ASSERT(!finished);
220 finished = true;
221 for (int i = 0; i + 1 < n_vertices; i++) {
222 stroke(canvas.transform(vertices[i]),
223 canvas.transform(vertices[i + 1]));
224 }
225 }
226 };
227
228 struct Circle {
229 Canvas &canvas;
230 Vector2 _center;
231 Vector4 _color;
232 real _radius;
233 bool finished;
234
235 TI_FORCE_INLINE Circle(Canvas &canvas, Vector2 center)
236 : canvas(canvas),
237 _center(center),
238 _color(canvas.context._color),
239 _radius(canvas.context._radius) {
240 finished = false;
241 }
242
243 TI_FORCE_INLINE Circle &color(Vector4 color) {
244 _color = color;
245 return *this;
246 }
247
248 TI_FORCE_INLINE Circle &color(real r, real g, real b, real a = 1) {
249 _color = Vector4(r, g, b, a);
250 return *this;
251 }
252
253 TI_FORCE_INLINE Circle &color(int r, int g, int b, int a = 255) {
254 _color = (1.0_f / 255) * Vector4(r, g, b, a);
255 return *this;
256 }
257
258 TI_FORCE_INLINE Circle &color(int c) {
259 return color(c / 65536, c / 256 % 256, c % 256, 255);
260 }
261
262 TI_FORCE_INLINE Circle &radius(real radius) {
263 _radius = radius;
264 return *this;
265 }
266
267 void finish() {
268 TI_ASSERT(finished == false);
269 finished = true;
270 auto center = canvas.transform(_center);
271 auto const canvas_width = canvas.img.get_width();
272 auto const canvas_height = canvas.img.get_height();
273 const auto r = _radius;
274 int i_lower = std::max(0, (int)std::ceil(center(0) - r));
275 int j_lower = std::max(0, (int)std::ceil(center(1) - r));
276 int i_higher = std::min((int)std::floor(center(0) + r), canvas_width - 1);
277 int j_higher =
278 std::min((int)std::floor(center(1) + r), canvas_height - 1);
279 const auto w = _color.w;
280 for (int i = i_lower; i <= i_higher; i++) {
281 for (int j = j_lower; j <= j_higher; j++) {
282 real dist = length(center - Vector2(i, j));
283 auto alpha = w * clamp(r - dist);
284 auto &dest = canvas.img[Vector2i(i, j)];
285 dest = lerp(alpha, dest, _color);
286 }
287 }
288 }
289
290 TI_FORCE_INLINE ~Circle() {
291 if (!finished)
292 finish();
293 }
294 };
295
296 public:
297 Array2D<Vector4> &img;
298 Matrix3 transform_matrix;
299
300 explicit Canvas(Array2D<Vector4> &img) : img(img) {
301 transform_matrix = Matrix3(Vector3(img.get_res().cast<real>(), 1.0_f));
302 }
303
304 TI_FORCE_INLINE Vector2 transform(Vector2 x) const {
305 return Vector2(transform_matrix * Vector3(x, 1.0_f));
306 }
307
308 TI_FORCE_INLINE Vector2 untransform(Vector2 x) const {
309 return Vector2(inversed(transform_matrix) * Vector3(x, 1.0_f));
310 }
311
312 std::vector<Circle> circles;
313 std::vector<Line> lines;
314
315 Circle &circle(Vector2 center) {
316 circles.emplace_back(*this, center);
317 return circles.back();
318 }
319
320 Circle &circle(real x, real y) {
321 circles.emplace_back(*this, Vector2(x, y));
322 return circles.back();
323 }
324
325 void circles_batched(int n,
326 std::size_t x,
327 uint32 color_single,
328 std::size_t color_array,
329 real radius_single,
330 std::size_t radius_array);
331
332 void circle_single(real x, real y, uint32 color, real radius);
333
334 void paths_batched(int n,
335 std::size_t a_,
336 std::size_t b_,
337 uint32 color_single,
338 std::size_t color_array,
339 real radius_single,
340 std::size_t radius_array);
341
342 void path_single(real x0,
343 real y0,
344 real x1,
345 real y1,
346 uint32 color,
347 real radius);
348
349 Line &path(real xa, real ya, real xb, real yb) {
350 return path(Vector2(xa, ya), Vector2(xb, yb));
351 }
352
353 Line &path() {
354 lines.emplace_back(*this);
355 return lines.back();
356 }
357
358 Line &path(Vector2 a, Vector2 b) {
359 lines.emplace_back(*this);
360 lines.back().path(a, b);
361 return lines.back();
362 }
363
364 Line &path(Vector2 a, Vector2 b, Vector2 c) {
365 lines.emplace_back(*this, a, b, c);
366 return lines.back();
367 }
368
369 Line &path(Vector2 a, Vector2 b, Vector2 c, Vector2 d) {
370 lines.emplace_back(*this, a, b, c, d);
371 return lines.back();
372 }
373
374 Line &rect(Vector2 a, Vector2 b) {
375 lines.emplace_back(*this, a, Vector2(a.x, b.y), b, Vector2(b.x, a.y));
376 return lines.back();
377 }
378
379 void line(Vector2 start, Vector2 end, Vector4 color) {
380 // convert to screen space
381 start = transform(start);
382 end = transform(end);
383 real len = length(end - start);
384 int samples = (int)len * 2 + 4;
385 for (int i = 0; i < samples; i++) {
386 real alpha = (1.0_f / (samples - 1)) * i;
387 Vector2i coord = (lerp(alpha, start, end)).floor().cast<int>();
388 if (img.inside(coord)) {
389 img[coord] = color;
390 }
391 }
392 }
393
394 void triangle(Vector2 a, Vector2 b, Vector2 c, Vector4 color);
395
396 void triangles_batched(int n,
397 std::size_t a_,
398 std::size_t b_,
399 std::size_t c_,
400 uint32 color_single,
401 std::size_t color_array);
402
403 void triangle_single(real x0,
404 real y0,
405 real x1,
406 real y1,
407 real x2,
408 real y2,
409 uint32 color);
410
411 void text(const std::string &str,
412 Vector2 position,
413 real size,
414 Vector4 color) {
415 position = transform(position);
416 std::string folder;
417 folder = fmt::format("{}/../../assets", lang::runtime_lib_dir());
418 auto ttf_path = fmt::format("{}/Go-Regular.ttf", folder);
419 img.write_text(ttf_path, str, size, position.x, position.y, color);
420 }
421
422 void clear(Vector4 color) {
423 circles.clear();
424 lines.clear();
425 img.reset(color);
426 }
427
428 void clear(uint32 c) {
429 clear(color_from_hex(c));
430 }
431
432 ~Canvas() {
433 }
434
435 void set_identity_transform_matrix() {
436 transform_matrix = Matrix3(1);
437 }
438};
439
440#if defined(TI_GUI_ANDROID)
441
442class GUIBaseAndroid {
443 public:
444 // @TODO
445};
446
447using GUIBase = GUIBaseAndroid;
448
449#endif
450
451#if defined(TI_GUI_X11)
452
453class CXImage;
454
455class GUIBaseX11 {
456 public:
457 void *display;
458 void *visual;
459 unsigned long window;
460 CXImage *img;
461 std::vector<char> wmDeleteMessage;
462};
463
464using GUIBase = GUIBaseX11;
465
466#endif
467
468#if defined(TI_GUI_WIN32)
469class GUIBaseWin32 {
470 public:
471 HWND hwnd;
472 HDC hdc;
473 COLORREF *data;
474 HDC src;
475 HBITMAP bitmap;
476};
477
478using GUIBase = GUIBaseWin32;
479#endif
480
481#if defined(TI_GUI_COCOA)
482class GUIBaseCocoa {
483 public:
484 id window, view;
485 std::size_t img_data_length;
486 std::vector<uint8_t> img_data;
487 std::atomic_bool window_received_close = false;
488 // Some key are called *modifier keys* and are not detected by regular key
489 // events in Cocoa. Instead, they will trigger a flags changed event.
490 // https://developer.apple.com/documentation/appkit/nseventtype/nseventtypeflagschanged?language=objc
491 //
492 // We have to:
493 // 1. check [event modifierFlags] to retrieve the key code,
494 // 2. maintain the press/released events on our own.
495 std::unordered_map<std::string, bool> active_modifier_flags;
496};
497
498using GUIBase = GUIBaseCocoa;
499#endif
500
501class TI_DLL_EXPORT GUI : public GUIBase {
502 public:
503 std::string window_name;
504 int width, height;
505 int frame_id = 0;
506 real frame_delta_limit = 1.0 / 60;
507 float64 start_time;
508 Array2D<Vector4> buffer;
509 std::vector<real> last_frame_interval;
510 std::unique_ptr<Canvas> canvas;
511 float64 last_frame_time;
512 bool key_pressed;
513 int should_close{0};
514 std::vector<std::string> log_entries;
515 Vector2i cursor_pos;
516 bool button_status[3];
517 int widget_height;
518 std::vector<std::unique_ptr<float>> widget_values;
519 bool show_gui;
520 bool fullscreen;
521 bool fast_gui;
522 uintptr_t fast_buf;
523
524 void set_mouse_pos(int x, int y) {
525 cursor_pos = Vector2i(x, y);
526 }
527
528 Vector2i widget_size = Vector2i(200, 40);
529
530 struct MouseEvent {
531 enum class Type { move, press, release };
532 Type type;
533 Vector2i pos;
534 bool button_status[3];
535 };
536
537 struct KeyEvent {
538 enum class Type { move, press, release };
539 Type type;
540 std::string key;
541 Vector2i pos;
542 Vector2i delta;
543 };
544
545 std::vector<KeyEvent> key_events;
546
547 struct Rect {
548 Vector2i pos;
549 Vector2i size;
550 TI_IO_DEF(pos, size);
551 Rect() {
552 }
553 Rect(Vector2i pos, Vector2i size) : pos(pos), size(size) {
554 }
555 bool inside(Vector2i p) {
556 return pos <= p && p < pos + size;
557 }
558 };
559
560 class Widget {
561 public:
562 Rect rect;
563 bool hover;
564
565 Widget() {
566 hover = false;
567 }
568
569 explicit Widget(Rect rect) : Widget() {
570 this->rect = rect;
571 }
572
573 bool inside(Vector2i p) {
574 return rect.inside(p);
575 }
576
577 virtual void mouse_event(MouseEvent e) {
578 }
579
580 virtual void redraw(Canvas &canvas) {
581 Vector4 color =
582 hover ? color_from_hex(widget_bg) : color_from_hex(widget_hover);
583 for (int i = 1; i < rect.size[0] - 1; i++) {
584 for (int j = 1; j < rect.size[1] - 1; j++) {
585 canvas.img[rect.pos[0] + i][rect.pos[1] + j] = color;
586 }
587 }
588 }
589
590 void set_hover(bool val) {
591 hover = val;
592 }
593
594 virtual ~Widget() {
595 }
596 };
597
598 std::vector<std::unique_ptr<Widget>> widgets;
599
600 class Button : public Widget {
601 public:
602 std::string text;
603
604 using CallbackType = std::function<void()>;
605 CallbackType callback;
606
607 Button(Rect rect, const std::string text, const CallbackType &callback)
608 : Widget(rect), text(text), callback(callback) {
609 }
610
611 void mouse_event(MouseEvent e) override {
612 if (e.type == MouseEvent::Type::release) {
613 callback();
614 }
615 }
616
617 void redraw(Canvas &canvas) override {
618 Widget::redraw(canvas);
619 int s = 32;
620 canvas.text(
621 text,
622 (rect.pos + Vector2i(2, rect.size[1] - 2)).template cast<real>(), s,
623 color_from_hex(text_color));
624 }
625 };
626
627 template <typename T>
628 class Slider : public Widget {
629 public:
630 std::string text;
631 T &val;
632 T minimum, maximum, step;
633
634 using CallbackType = std::function<void()>;
635 CallbackType callback;
636
637 const int slider_padding = 10;
638
639 Slider(Rect rect,
640 const std::string text,
641 T &val,
642 T minimum,
643 T maximum,
644 T step)
645 : Widget(rect),
646 text(text),
647 val(val),
648 minimum(minimum),
649 maximum(maximum),
650 step(step) {
651 }
652
653 void mouse_event(MouseEvent e) override {
654 if ((e.type == MouseEvent::Type::press ||
655 e.type == MouseEvent::Type::move) &&
656 e.button_status[0]) {
657 real alpha = clamp(real(e.pos[0] - rect.pos[0] - slider_padding) /
658 (rect.size[0] - slider_padding * 2));
659 real offset = 0.0_f;
660 if (std::is_integral<T>::value) {
661 offset = 0.5_f;
662 }
663 val = static_cast<T>(alpha * (maximum - minimum) + minimum + offset);
664 }
665 }
666
667 void redraw(Canvas &canvas) override {
668 Widget::redraw(canvas);
669 int s = 16;
670 auto text_with_value = text;
671 if (std::is_integral<T>::value) {
672 text_with_value += fmt::format(": {}", val);
673 } else {
674 text_with_value += fmt::format(": {:.3f}", val);
675 }
676 canvas.text(
677 text_with_value,
678 (rect.pos + Vector2i(2, rect.size[1] - 2)).template cast<real>(), s,
679 color_from_hex(text_color));
680 int slider_start = slider_padding,
681 slider_end = rect.size[0] - slider_padding;
682 for (int i = slider_start; i < slider_end; i++) {
683 for (int j = slider_padding; j < slider_padding + 3; j++) {
684 canvas.img[rect.pos[0] + i][rect.pos[1] + j] =
685 color_from_hex(slider_bar_color);
686 }
687 }
688 auto alpha = (val - minimum) / real(maximum - minimum);
689 canvas
690 .circle(rect.pos.template cast<real>() +
691 Vector2(lerp(alpha, slider_start, slider_end),
692 slider_padding + 1))
693 .radius(5)
694 .color(color_from_hex(slider_circle_color));
695 }
696 };
697
698 template <typename T>
699 class Label : public Widget {
700 public:
701 std::string text;
702 T &val;
703
704 const int slider_padding = 5;
705
706 Label(Rect rect, const std::string text, T &val)
707 : Widget(rect), text(text), val(val) {
708 }
709
710 void redraw(Canvas &canvas) override {
711 Widget::redraw(canvas);
712 int s = 16;
713 auto text_with_value = text;
714 if (std::is_integral<T>::value) {
715 text_with_value += fmt::format(": {}", val);
716 } else {
717 text_with_value += fmt::format(": {:.3f}", val);
718 }
719 canvas.text(
720 text_with_value,
721 (rect.pos + Vector2i(2, rect.size[1] - 2)).template cast<real>(), s,
722 color_from_hex(text_color));
723 }
724 };
725
726 Rect make_widget_rect(int h) {
727 widget_height += h;
728 return Rect(Vector2i(width - widget_size[0], height - widget_height),
729 Vector2i(widget_size[0], h));
730 }
731
732 GUI &button(std::string text, const Button::CallbackType &callback) {
733 widgets.push_back(std::make_unique<Button>(make_widget_rect(widget_size[1]),
734 text, callback));
735 return *this;
736 }
737
738 template <typename T>
739 GUI &slider(std::string text, T &val, T minimum, T maximum, T step = 1) {
740 widgets.push_back(std::make_unique<Slider<T>>(
741 make_widget_rect(widget_size[1]), text, val, minimum, maximum, step));
742 return *this;
743 }
744
745 template <typename T>
746 GUI &label(std::string text, T &val) {
747 widgets.push_back(std::make_unique<Label<T>>(
748 make_widget_rect(widget_size[1] / 2), text, val));
749 return *this;
750 }
751
752 void process_event();
753
754 void send_window_close_message() {
755 key_events.push_back(
756 GUI::KeyEvent{GUI::KeyEvent::Type::press, "WMClose", cursor_pos});
757 should_close++;
758 }
759
760 void mouse_event(MouseEvent e) {
761 if (e.type == MouseEvent::Type::press) {
762 button_status[0] = true;
763 }
764 if (e.type == MouseEvent::Type::release) {
765 button_status[0] = false;
766 }
767 e.button_status[0] = button_status[0];
768 for (auto &w : widgets) {
769 if (w->inside(e.pos)) {
770 w->mouse_event(e);
771 }
772 }
773 }
774
775 explicit GUI(const std::string &window_name,
776 int width = 800,
777 int height = 800,
778 bool show_gui = true,
779 bool fullscreen = true,
780 bool fast_gui = false,
781 uintptr_t fast_buf = 0,
782 bool normalized_coord = true)
783 : window_name(window_name),
784 width(width),
785 height(height),
786 key_pressed(false),
787 show_gui(show_gui),
788 fullscreen(fullscreen),
789 fast_gui(fast_gui),
790 fast_buf(fast_buf) {
791 memset(button_status, 0, sizeof(button_status));
792 start_time = taichi::Time::get_time();
793 buffer.initialize(Vector2i(width, height));
794 canvas = std::make_unique<Canvas>(buffer);
795 last_frame_time = taichi::Time::get_time();
796 if (!normalized_coord) {
797 canvas->set_identity_transform_matrix();
798 }
799 widget_height = 0;
800 if (show_gui) {
801 initialize_window();
802 }
803 }
804
805 explicit GUI(const std::string &window_name,
806 Vector2i res,
807 bool show_gui,
808 bool fullscreen = true,
809 bool fast_gui = false,
810 uintptr_t fast_buf = 0,
811 bool normalized_coord = true)
812 : GUI(window_name,
813 res[0],
814 res[1],
815 show_gui,
816 fullscreen,
817 fast_gui,
818 fast_buf,
819 normalized_coord) {
820 }
821
822 void create_window();
823
824 void initialize_window() {
825 create_window();
826 set_title(window_name);
827 }
828
829 Canvas &get_canvas() {
830 return *canvas;
831 }
832
833 void redraw();
834
835 void set_title(std::string title);
836
837 void redraw_widgets() {
838 auto old_transform_matrix = canvas->transform_matrix;
839 canvas->set_identity_transform_matrix();
840 for (auto &w : widgets) {
841 w->set_hover(w->inside(cursor_pos));
842 w->redraw(*canvas);
843 }
844 canvas->transform_matrix = old_transform_matrix;
845 }
846
847 void update() {
848 frame_id++;
849 if (show_gui) {
850 taichi::Time::wait_until(last_frame_time + frame_delta_limit);
851 auto this_frame_time = taichi::Time::get_time();
852 if (last_frame_time != 0) {
853 last_frame_interval.push_back(this_frame_time - last_frame_time);
854 }
855 last_frame_time = this_frame_time;
856 // Some old examples / users don't even provide a `break` statement for us
857 // to terminate loop. So we have to terminate the program with
858 // RuntimeError if ti.GUI.EXIT event is not processed. Pretty like
859 // SIGTERM, you can hook it, but you have to terminate after your handler
860 // is done.
861 if (should_close) {
862 if (++should_close > 5) {
863 // if the event is not processed in 5 frames, raise RuntimeError
864 throw std::string(
865 "Window close button clicked, exiting... (use `while "
866 "gui.running` "
867 "to exit gracefully)");
868 }
869 }
870 while (last_frame_interval.size() > 30) {
871 last_frame_interval.erase(last_frame_interval.begin());
872 }
873 auto real_fps = last_frame_interval.size() /
874 (std::accumulate(last_frame_interval.begin(),
875 last_frame_interval.end(), 0.0_f));
876 redraw_widgets();
877 redraw();
878 process_event();
879 if (frame_id % 10 == 0)
880 set_title(fmt::format("{} ({:.2f} FPS)", window_name, real_fps));
881 }
882 }
883
884 bool has_key_event() {
885 return !!key_events.size();
886 }
887
888 void wait_key_event() {
889 while (!key_events.size()) {
890 update();
891 }
892 }
893
894 KeyEvent get_key_event_head() {
895 return key_events[0];
896 }
897
898 Vector2 get_cursor_pos() {
899 return canvas->untransform(Vector2(cursor_pos));
900 }
901
902 void pop_key_event_head() {
903 key_events.erase(key_events.begin());
904 }
905
906 void wait_key() {
907 while (true) {
908 key_pressed = false;
909 update();
910 if (key_pressed) {
911 break;
912 }
913 }
914 }
915
916 Vector2 canvas_untransform(Vector2i pos) {
917 return canvas->untransform(Vector2(pos));
918 }
919
920 void draw_log() {
921 for (int i = 0; i < (int)log_entries.size(); i++) {
922 canvas->text(log_entries[i], Vector2(0.0, -0.02_f * i), 15, Vector4(0));
923 }
924 }
925
926 void log(std::string entry) {
927 log_entries.push_back(entry);
928 if (log_entries.size() > 15) {
929 log_entries.erase(log_entries.begin());
930 }
931 }
932
933 void screenshot(std::string filename = "") {
934 if (filename == "") {
935 char timestamp[80];
936 std::time_t t = std::time(nullptr);
937 std::tm tstruct = *localtime(&t);
938 std::strftime(timestamp, sizeof(timestamp), "%Y-%m-%d_%H-%M-%S",
939 &tstruct);
940 filename = std::string(timestamp) + ".png";
941 }
942 canvas->img.write_as_image(filename);
943 }
944
945 ~GUI();
946};
947
948} // namespace taichi
949