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 | |
30 | namespace taichi { |
31 | |
32 | TI_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) |
37 | constexpr uint32 text_color = 0x02547D; |
38 | constexpr uint32 widget_bg = 0x02BEC4; |
39 | constexpr uint32 widget_hover = 0xA9E8DC; |
40 | constexpr uint32 slider_bar_color = text_color; |
41 | constexpr uint32 slider_circle_color = 0x0284A8; |
42 | #else |
43 | constexpr uint32 text_color = 0x111111; |
44 | constexpr uint32 widget_bg = 0xAAAAAA; |
45 | constexpr uint32 widget_hover = 0xCCCCCC; |
46 | constexpr uint32 slider_bar_color = 0x333333; |
47 | constexpr uint32 slider_circle_color = 0x555555; |
48 | #endif |
49 | |
50 | class 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 | |
442 | class GUIBaseAndroid { |
443 | public: |
444 | // @TODO |
445 | }; |
446 | |
447 | using GUIBase = GUIBaseAndroid; |
448 | |
449 | #endif |
450 | |
451 | #if defined(TI_GUI_X11) |
452 | |
453 | class CXImage; |
454 | |
455 | class GUIBaseX11 { |
456 | public: |
457 | void *display; |
458 | void *visual; |
459 | unsigned long window; |
460 | CXImage *img; |
461 | std::vector<char> wmDeleteMessage; |
462 | }; |
463 | |
464 | using GUIBase = GUIBaseX11; |
465 | |
466 | #endif |
467 | |
468 | #if defined(TI_GUI_WIN32) |
469 | class GUIBaseWin32 { |
470 | public: |
471 | HWND hwnd; |
472 | HDC hdc; |
473 | COLORREF *data; |
474 | HDC src; |
475 | HBITMAP bitmap; |
476 | }; |
477 | |
478 | using GUIBase = GUIBaseWin32; |
479 | #endif |
480 | |
481 | #if defined(TI_GUI_COCOA) |
482 | class 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 | |
498 | using GUIBase = GUIBaseCocoa; |
499 | #endif |
500 | |
501 | class 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 | |