ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
gui.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
9 
10 #include <FileSystem.h>
11 #include <Image.h>
13 #include <pybind11/detail/common.h>
14 #include <pybind11/functional.h>
15 
16 #include "pybind/docstring.h"
18 #include "t/geometry/Image.h"
27 #include "visualization/gui/Gui.h"
54 
55 namespace cloudViewer {
56 namespace visualization {
57 namespace gui {
58 
60 public:
61  PythonUnlocker() { unlocker_ = nullptr; }
63  if (unlocker_) { // paranoia; this shouldn't happen
64  delete unlocker_;
65  }
66  }
67 
68  void unlock() { unlocker_ = new py::gil_scoped_release(); }
69  void relock() {
70  delete unlocker_;
71  unlocker_ = nullptr;
72  }
73 
74 private:
75  py::gil_scoped_release *unlocker_;
76 };
77 
78 class PyWindow : public Window {
79  using Super = Window;
80 
81 public:
82  explicit PyWindow(const std::string &title, int flags = 0)
83  : Super(title, flags) {}
84  PyWindow(const std::string &title, int width, int height, int flags = 0)
85  : Super(title, width, height, flags) {}
86  PyWindow(const std::string &title,
87  int x,
88  int y,
89  int width,
90  int height,
91  int flags = 0)
92  : Super(title, x, y, width, height, flags) {}
93 
94  std::function<void(const LayoutContext &)> on_layout_;
95 
96 protected:
97  void Layout(const LayoutContext &context) override {
98  if (on_layout_) {
99  // the Python callback sizes the children
101  // and then we need to layout the children
102  for (auto child : GetChildren()) {
103  child->Layout(context);
104  }
105  } else {
107  }
108  }
109 };
110 
111 // atexit: Filament crashes if the engine was not destroyed before exit().
112 // As far as I can tell, the bluegl mutex, which is a static variable,
113 // gets destroyed before the render thread gets around to calling
114 // bluegl::unbind(), thus crashing. So, we need to make sure Filament gets
115 // cleaned up before C++ starts cleaning up static variables. But we don't want
116 // to clean up this way unless something catastrophic happens (e.g. the Python
117 // interpreter is exiting due to a fatal exception). Some cases we need to
118 // consider:
119 // 1) exception before calling Application.instance.run()
120 // 2) exception during Application.instance.run(), namely within a UI callback
121 // 3) exception after Application.instance.run() successfully finishes
122 // If Python is exiting normally, then Application::Run() should have already
123 // cleaned up Filament. So if we still need to clean up Filament at exit(),
124 // we must be panicking. It is a little difficult to check this, though, but
125 // Application::OnTerminate() should work even if we've already cleaned up,
126 // it will just end up being a no-op.
127 bool g_installed_atexit = false;
129 
131  if (!g_installed_atexit) {
132  atexit(cleanup_filament_atexit);
133  }
134 }
135 
136 void InitializeForPython(std::string resource_path /*= ""*/,
137  bool headless /*= false*/) {
138  if (resource_path.empty()) {
139  // We need to find the resources directory. Fortunately,
140  // Python knows where the module lives (cloudViewer.__file__
141  // is the path to
142  // __init__.py), so we can use that to find the
143  // resources included in the wheel.
144  py::object cv3d = py::module::import("cloudViewer");
145  auto cv3d_init_path = cv3d.attr("__file__").cast<std::string>();
146  auto module_path =
148  resource_path = module_path + "/resources";
149  }
150  Application::GetInstance().Initialize(resource_path.c_str());
151  // NOTE: The PyOffscreenRenderer takes care of cleaning itself up so the
152  // atext is not necessary
153  if (!headless) {
155  }
156 }
157 
158 std::shared_ptr<geometry::Image> RenderToImageWithoutWindow(
159  rendering::CloudViewerScene *scene, int width, int height) {
161  scene->GetRenderer(), scene->GetView(), scene->GetScene(), width,
162  height);
163 }
164 
165 std::shared_ptr<geometry::Image> RenderToDepthImageWithoutWindow(
167  int width,
168  int height,
169  bool z_in_view_space /* = false */) {
171  scene->GetRenderer(), scene->GetView(), scene->GetScene(), width,
172  height, z_in_view_space);
173 }
174 
176 
177 class PyImageWidget : public ImageWidget {
178  using Super = ImageWidget;
179 
180 public:
184  explicit PyImageWidget(const char *image_path) : Super(image_path) {}
186  explicit PyImageWidget(std::shared_ptr<cloudViewer::geometry::Image> image)
187  : Super(image) {}
189  explicit PyImageWidget(
190  std::shared_ptr<cloudViewer::t::geometry::Image> image)
191  : Super(image) {}
195  explicit PyImageWidget(
197  float u0 = 0.0f,
198  float v0 = 0.0f,
199  float u1 = 1.0f,
200  float v1 = 1.0f)
201  : Super(texture_id, u0, v0, u1, v1) {}
202 
203  ~PyImageWidget() = default;
204 
205  void SetOnMouse(std::function<int(const MouseEvent &)> f) { on_mouse_ = f; }
206  void SetOnKey(std::function<int(const KeyEvent &)> f) { on_key_ = f; }
207 
208  Widget::EventResult Mouse(const MouseEvent &e) override {
209  if (on_mouse_) {
210  switch (EventCallbackResult(on_mouse_(e))) {
214  auto result = Super::Mouse(e);
217  }
218  return result;
219  }
221  default:
222  return Super::Mouse(e);
223  }
224  } else {
225  return Super::Mouse(e);
226  }
227  }
228 
229  Widget::EventResult Key(const KeyEvent &e) override {
230  if (on_key_) {
231  switch (EventCallbackResult(on_key_(e))) {
235  auto result = Super::Key(e);
238  }
239  return result;
240  }
242  default:
243  return Super::Key(e);
244  }
245  } else {
246  return Super::Key(e);
247  }
248  }
249 
250 private:
251  std::function<int(const MouseEvent &)> on_mouse_;
252  std::function<int(const KeyEvent &)> on_key_;
253 };
254 
255 class PySceneWidget : public SceneWidget {
256  using Super = SceneWidget;
257 
258 public:
259  void SetOnMouse(std::function<int(const MouseEvent &)> f) { on_mouse_ = f; }
260  void SetOnKey(std::function<int(const KeyEvent &)> f) { on_key_ = f; }
261 
262  Widget::EventResult Mouse(const MouseEvent &e) override {
263  if (on_mouse_) {
264  switch (EventCallbackResult(on_mouse_(e))) {
268  auto result = Super::Mouse(e);
271  }
272  return result;
273  }
275  default:
276  return Super::Mouse(e);
277  }
278  } else {
279  return Super::Mouse(e);
280  }
281  }
282 
283  Widget::EventResult Key(const KeyEvent &e) override {
284  if (on_key_) {
285  switch (EventCallbackResult(on_key_(e))) {
289  auto result = Super::Key(e);
292  }
293  return result;
294  }
296  default:
297  return Super::Key(e);
298  }
299  } else {
300  return Super::Key(e);
301  }
302  }
303 
304 private:
305  std::function<int(const MouseEvent &)> on_mouse_;
306  std::function<int(const KeyEvent &)> on_key_;
307 };
308 
309 void pybind_gui_classes(py::module &m_gui) {
310  // ---- FontStyle ----
311  py::native_enum<FontStyle>(m_gui, "FontStyle", "enum.Enum", "Font style")
312  .value("NORMAL", FontStyle::NORMAL)
313  .value("BOLD", FontStyle::BOLD)
314  .value("ITALIC", FontStyle::ITALIC)
315  .value("BOLD_ITALIC", FontStyle::BOLD_ITALIC)
316  .finalize();
317 
318  // ---- FontDescription ----
319  py::class_<FontDescription> fd(m_gui, "FontDescription",
320  "Class to describe a custom font");
321  fd.def_readonly_static("SANS_SERIF", &FontDescription::SANS_SERIF,
322  "Name of the default sans-serif font that comes "
323  "with CloudViewer")
324  .def_readonly_static(
325  "MONOSPACE", &FontDescription::MONOSPACE,
326  "Name of the default monospace font that comes "
327  "with CloudViewer")
328  .def(py::init<const char *, FontStyle, int>(),
329  "typeface"_a = FontDescription::SANS_SERIF,
330  "style"_a = FontStyle::NORMAL, "point_size"_a = 0,
331  "Creates a FontDescription. 'typeface' is a path to a "
332  "TrueType (.ttf), TrueType Collection (.ttc), or "
333  "OpenType (.otf) file, or it is the name of the font, "
334  "in which case the system font paths will be searched "
335  "to find the font file. This typeface will be used for "
336  "roman characters (Extended Latin, that is, European "
337  "languages")
338  .def("add_typeface_for_language",
340  "Adds code points outside Extended Latin from the specified "
341  "typeface. Supported languages are:\n"
342  " 'ja' (Japanese)\n"
343  " 'ko' (Korean)\n"
344  " 'th' (Thai)\n"
345  " 'vi' (Vietnamese)\n"
346  " 'zh' (Chinese, 2500 most common characters, 50 MB per "
347  "window)\n"
348  " 'zh_all' (Chinese, all characters, ~200 MB per window)\n"
349  "All other languages will be assumed to be Cyrillic. "
350  "Note that generally fonts do not have CJK glyphs unless they "
351  "are specifically a CJK font, although operating systems "
352  "generally use a CJK font for you. We do not have the "
353  "information necessary to do this, so you will need to "
354  "provide a font that has the glyphs you need. In particular, "
355  "common fonts like 'Arial', 'Helvetica', and SANS_SERIF do "
356  "not contain CJK glyphs.")
357  .def("add_typeface_for_code_points",
359  "Adds specific code points from the typeface. This is useful "
360  "for selectively adding glyphs, for example, from an icon "
361  "font.");
362 
363  // ---- Application ----
364  py::class_<Application> application(m_gui, "Application",
365  "Global application singleton. This "
366  "owns the menubar, windows, and event "
367  "loop");
368  application
369  .def_readonly_static("DEFAULT_FONT_ID",
371  .def("__repr__",
372  [](const Application &app) {
373  return std::string("Application singleton instance");
374  })
375  .def_property_readonly_static(
376  "instance",
377  // Seems like we ought to be able to specify
378  // &Application::GetInstance but that gives runtime errors
379  // about number of arguments. It seems that property calls
380  // are made from an object, so that object needs to be in
381  // the function signature.
382  [](py::object) -> Application & {
383  return Application::GetInstance();
384  },
385  py::return_value_policy::reference,
386  "Gets the Application singleton (read-only)")
387  .def(
388  "initialize",
389  [](Application &instance) { InitializeForPython(); },
390  "Initializes the application, using the resources included "
391  "in the wheel. One of the `initialize` functions _must_ be "
392  "called prior to using anything in the gui module")
393  .def(
394  "initialize",
395  [](Application &instance, const char *resource_dir) {
396  InitializeForPython(resource_dir);
397  },
398  "Initializes the application with location of the "
399  "resources "
400  "provided by the caller. One of the `initialize` functions "
401  "_must_ be called prior to using anything in the gui "
402  "module")
403  .def("add_font", &Application::AddFont,
404  "Adds a font. Must be called after initialize() and before "
405  "a window is created. Returns the font id, which can be used "
406  "to change the font in widgets such as Label which support "
407  "custom fonts.")
408  .def("set_font", &Application::SetFont,
409  "Changes the contents of an existing font, for instance, the "
410  "default font.")
411  .def(
412  "create_window",
413  [](Application &instance, const std::string &title,
414  int width, int height, int x, int y, int flags) {
415  std::shared_ptr<PyWindow> w;
416  if (x < 0 && y < 0 && width < 0 && height < 0) {
417  w.reset(new PyWindow(title, flags));
418  } else if (x < 0 && y < 0) {
419  w.reset(new PyWindow(title, width, height, flags));
420  } else {
421  w.reset(new PyWindow(title, x, y, width, height,
422  flags));
423  }
424  instance.AddWindow(w);
425  return w.get();
426  },
427  "title"_a = std::string(), "width"_a = -1, "height"_a = -1,
428  "x"_a = -1, "y"_a = -1, "flags"_a = 0,
429  "Creates a window and adds it to the application. "
430  "To programmatically destroy the window do window.close()."
431  "Usage: create_window(title, width, height, x, y, flags). "
432  "x, y, and flags are optional.")
433  // We need to tell RunOneTick() not to cleanup. Run() and
434  // RunOneTick() assume a C++ desktop application approach of
435  // init -> run -> cleanup. More problematic for us is that Filament
436  // crashes if we don't cleanup. Also, we don't want to force Python
437  // script writers to remember to cleanup; this is Python, not C++
438  // where you expect to need to thinking about cleanup up after
439  // yourself. So as a Python-script-app, the cleanup happens atexit.
440  // (Init is still required of the script writer.) This means that
441  // run() should NOT cleanup, as it might be called several times
442  // to run a UI to visualize the result of a computation.
443  .def(
444  "run",
445  [](Application &instance) {
446  PythonUnlocker unlocker;
447  while (instance.RunOneTick(unlocker, false)) {
448  // Enable Ctrl-C to kill Python
449  if (PyErr_CheckSignals() != 0) {
450  throw py::error_already_set();
451  }
452  }
453  },
454  "Runs the event loop. After this finishes, all windows and "
455  "widgets should be considered uninitialized, even if they "
456  "are still held by Python variables. Using them is unsafe, "
457  "even if run() is called again.")
458  .def(
459  "run_one_tick",
460  [](Application &instance) {
461  PythonUnlocker unlocker;
462  auto result = instance.RunOneTick(unlocker, false);
463  // Enable Ctrl-C to kill Python
464  if (PyErr_CheckSignals() != 0) {
465  throw py::error_already_set();
466  }
467  return result;
468  },
469  "Runs the event loop once, returns True if the app is "
470  "still running, or False if all the windows have closed "
471  "or quit() has been called.")
472  .def(
473  "render_to_image",
474  [](Application &instance,
476  int height) {
477  return RenderToImageWithoutWindow(scene, width, height);
478  },
479  "Renders a scene to an image and returns the image. If you "
480  "are rendering without a visible window you should use "
481  "cloudViewer.visualization.rendering.RenderToImage instead")
482  .def(
483  "quit", [](Application &instance) { instance.Quit(); },
484  "Closes all the windows, exiting as a result")
485  .def(
486  "add_window",
487  // Q: Why not just use &Application::AddWindow here?
488  // A: Because then AddWindow gets passed a shared_ptr with
489  // a use_count of 0 (but with the correct value for
490  // .get()), so it never gets freed, and then Filament
491  // doesn't clean up correctly. TakeOwnership() will
492  // create the shared_ptr properly.
493  [](Application &instance, UnownedPointer<Window> window) {
494  instance.AddWindow(TakeOwnership(window));
495  },
496  "Adds a window to the application. This is only necessary "
497  "when "
498  "creating object that is a Window directly, rather than "
499  "with "
500  "create_window")
501  .def("run_in_thread", &Application::RunInThread,
502  "Runs function in a separate thread. Do not call GUI "
503  "functions on this thread, call post_to_main_thread() if "
504  "this thread needs to change the GUI.")
505  .def("post_to_main_thread", &Application::PostToMainThread,
506  py::call_guard<py::gil_scoped_release>(),
507  "Runs the provided function on the main thread. This can "
508  "be used to execute UI-related code at a safe point in "
509  "time. If the UI changes, you will need to manually "
510  "request a redraw of the window with w.post_redraw()")
511  .def_property("menubar", &Application::GetMenubar,
513  "The Menu for the application (initially None)")
514  .def_property_readonly("now", &Application::Now,
515  "Returns current time in seconds")
516  // Note: we cannot export AddWindow and RemoveWindow
517  .def_property_readonly("resource_path",
519  "Returns a string with the path to the "
520  "resources directory");
521 
522  // ---- LayoutContext ----
523  py::class_<LayoutContext> lc(
524  m_gui, "LayoutContext",
525  "Context passed to Window's on_layout callback");
526  // lc.def_readonly("theme", &LayoutContext::theme);
527  // Pybind can't return a reference (since Theme is a unique_ptr), so
528  // return a copy instead.
529  lc.def_property_readonly("theme",
530  [](const LayoutContext &context) -> Theme {
531  return context.theme;
532  });
533 
534  // ---- Window ----
535  // Pybind appears to need to know about the base class. It doesn't have
536  // to be named the same as the C++ class, though. The holder object cannot
537  // be a shared_ptr or we can crash (see comment for UnownedPointer).
538  py::class_<Window, UnownedPointer<Window>> window_base(
539  m_gui, "WindowBase", "Application window");
540  py::class_<PyWindow, UnownedPointer<PyWindow>, Window> window(
541  m_gui, "Window",
542  "Application window. Create with "
543  "Application.instance.create_window().");
544  window.def("__repr__",
545  [](const PyWindow &w) { return "Application window"; })
546  .def(
547  "add_child",
548  [](PyWindow &w, UnownedPointer<Widget> widget) {
549  w.AddChild(TakeOwnership<Widget>(widget));
550  },
551  "Adds a widget to the window")
552  .def_property("os_frame", &PyWindow::GetOSFrame,
554  "Window rect in OS coords, not device pixels")
555  .def_property("title", &PyWindow::GetTitle, &PyWindow::SetTitle,
556  "Returns the title of the window")
557  .def("size_to_fit", &PyWindow::SizeToFit,
558  "Sets the width and height of window to its preferred size")
559  .def_property("size", &PyWindow::GetSize, &PyWindow::SetSize,
560  "The size of the window in device pixels, including "
561  "menubar (except on macOS)")
562  .def_property_readonly(
563  "content_rect", &PyWindow::GetContentRect,
564  "Returns the frame in device pixels, relative "
565  " to the window, which is available for widgets "
566  "(read-only)")
567  .def_property_readonly(
568  "scaling", &PyWindow::GetScaling,
569  "Returns the scaling factor between OS pixels "
570  "and device pixels (read-only)")
571  .def_property_readonly("is_visible", &PyWindow::IsVisible,
572  "True if window is visible (read-only)")
573  .def("set_on_key", &PyWindow::SetOnKeyEvent,
574  "Sets a callback for key events. This callback is passed "
575  "a KeyEvent object. The callback must return "
576  "True to stop more dispatching or False to dispatch"
577  "to focused widget")
578  .def("show", &PyWindow::Show, "Shows or hides the window")
579  .def("close", &PyWindow::Close,
580  "Closes the window and destroys it, unless an on_close "
581  "callback cancels the close.")
582  .def("set_needs_layout", &PyWindow::SetNeedsLayout,
583  "Flags window to re-layout")
584  .def("post_redraw", &PyWindow::PostRedraw,
585  "Sends a redraw message to the OS message queue")
586  .def_property_readonly("is_active_window",
588  "True if the window is currently the active "
589  "window (read-only)")
590  .def("set_focus_widget", &PyWindow::SetFocusWidget,
591  "Makes specified widget have text focus")
592  .def("set_on_menu_item_activated",
594  "Sets callback function for menu item: callback()")
595  .def("set_on_tick_event", &PyWindow::SetOnTickEvent,
596  "Sets callback for tick event. Callback takes no arguments "
597  "and must return True if a redraw is needed (that is, if "
598  "any widget has changed in any fashion) or False if nothing "
599  "has changed")
600  .def("set_on_close", &PyWindow::SetOnClose,
601  "Sets a callback that will be called when the window is "
602  "closed. The callback is given no arguments and should return "
603  "True to continue closing the window or False to cancel the "
604  "close")
605  .def(
606  "set_on_layout",
607  [](PyWindow *w,
608  std::function<void(const LayoutContext &)> f) {
609  w->on_layout_ = f;
610  },
611  "Sets a callback function that manually sets the frames of "
612  "children of the window. Callback function will be called "
613  "with one argument: gui.LayoutContext")
614  .def_property_readonly("theme", &PyWindow::GetTheme,
615  "Get's window's theme info")
616  .def(
617  "show_dialog",
618  [](PyWindow &w, UnownedPointer<Dialog> dlg) {
619  w.ShowDialog(TakeOwnership<Dialog>(dlg));
620  },
621  "Displays the dialog")
622  .def("close_dialog", &PyWindow::CloseDialog,
623  "Closes the current dialog")
624  .def("show_message_box", &PyWindow::ShowMessageBox,
625  "Displays a simple dialog with a title and message and okay "
626  "button")
627  .def("show_menu", &PyWindow::ShowMenu,
628  "show_menu(show): shows or hides the menu in the window, "
629  "except on macOS since the menubar is not in the window "
630  "and all applications must have a menubar.")
631  .def_property_readonly(
632  "renderer", &PyWindow::GetRenderer,
633  "Gets the rendering.Renderer object for the Window");
634 
635  // ---- Menu ----
636  py::class_<Menu, UnownedPointer<Menu>> menu(m_gui, "Menu",
637  "A menu, possibly a menu tree");
638  menu.def(py::init<>())
639  .def(
640  "add_item",
641  [](UnownedPointer<Menu> menu, const char *text,
642  int item_id) { menu->AddItem(text, item_id); },
643  "Adds a menu item with id to the menu")
644  .def(
645  "add_menu",
646  [](UnownedPointer<Menu> menu, const char *text,
647  UnownedPointer<Menu> submenu) {
648  menu->AddMenu(text, TakeOwnership<Menu>(submenu));
649  },
650  "Adds a submenu to the menu")
651  .def("add_separator", &Menu::AddSeparator,
652  "Adds a separator to the menu")
653  .def(
654  "set_enabled",
655  [](UnownedPointer<Menu> menu, int item_id, bool enabled) {
656  menu->SetEnabled(item_id, enabled);
657  },
658  "Sets menu item enabled or disabled")
659  .def(
660  "is_checked",
661  [](UnownedPointer<Menu> menu, int item_id) -> bool {
662  return menu->IsChecked(item_id);
663  },
664  "Returns True if menu item is checked")
665  .def(
666  "set_checked",
667  [](UnownedPointer<Menu> menu, int item_id, bool checked) {
668  menu->SetChecked(item_id, checked);
669  },
670  "Sets menu item (un)checked");
671 
672  // ---- Color ----
673  py::class_<Color> color(m_gui, "Color", "Stores color for gui classes");
674  color.def(py::init([](float r, float g, float b, float a) {
675  return new Color(r, g, b, a);
676  }),
677  "r"_a = 1.0, "g"_a = 1.0, "b"_a = 1.0, "a"_a = 1.0)
678  .def_property_readonly(
679  "red", &Color::GetRed,
680  "Returns red channel in the range [0.0, 1.0] "
681  "(read-only)")
682  .def_property_readonly(
683  "green", &Color::GetGreen,
684  "Returns green channel in the range [0.0, 1.0] "
685  "(read-only)")
686  .def_property_readonly(
687  "blue", &Color::GetBlue,
688  "Returns blue channel in the range [0.0, 1.0] "
689  "(read-only)")
690  .def_property_readonly(
691  "alpha", &Color::GetAlpha,
692  "Returns alpha channel in the range [0.0, 1.0] "
693  "(read-only)")
694  .def("set_color", &Color::SetColor,
695  "Sets red, green, blue, and alpha channels, (range: [0.0, "
696  "1.0])",
697  "r"_a, "g"_a, "b"_a, "a"_a = 1.0);
698 
699  // ---- Theme ----
700  // Note: no constructor because themes are created by CloudViewer
701  py::class_<Theme> theme(m_gui, "Theme",
702  "Theme parameters such as colors used for drawing "
703  "widgets (read-only)");
704  theme.def_readonly("font_size", &Theme::font_size,
705  "Font size (which is also the conventional size of the "
706  "em unit) (read-only)")
707  .def_readonly("default_margin", &Theme::default_margin,
708  "Good default value for margins, useful for layouts "
709  "(read-only)")
710  .def_readonly("default_layout_spacing",
712  "Good value for the spacing parameter in layouts "
713  "(read-only)");
714 
715  // ---- Rect ----
716  py::class_<Rect> rect(m_gui, "Rect", "Represents a widget frame");
717  rect.def(py::init<>())
718  .def(py::init<int, int, int, int>())
719  .def(py::init([](float x, float y, float w, float h) {
720  return Rect(int(std::round(x)), int(std::round(y)),
721  int(std::round(w)), int(std::round(h)));
722  }))
723  .def("__repr__",
724  [](const Rect &r) {
725  std::stringstream s;
726  s << "Rect (" << r.x << ", " << r.y << "), " << r.width
727  << " x " << r.height;
728  return s.str();
729  })
730  .def_readwrite("x", &Rect::x)
731  .def_readwrite("y", &Rect::y)
732  .def_readwrite("width", &Rect::width)
733  .def_readwrite("height", &Rect::height)
734  .def("get_left", &Rect::GetLeft)
735  .def("get_right", &Rect::GetRight)
736  .def("get_top", &Rect::GetTop)
737  .def("get_bottom", &Rect::GetBottom);
738 
739  // ---- Size ----
740  py::class_<Size> size(m_gui, "Size", "Size object");
741  size.def(py::init<>())
742  .def(py::init<int, int>())
743  .def(py::init([](float w, float h) {
744  return Size(int(std::round(w)), int(std::round(h)));
745  }))
746  .def("__repr__",
747  [](const Size &sz) {
748  std::stringstream s;
749  s << "Size (" << sz.width << ", " << sz.height << ")";
750  return s.str();
751  })
752  .def_readwrite("width", &Size::width)
753  .def_readwrite("height", &Size::height);
754 
755  // ---- Widget ----
756  // The holder for Widget and all derived classes is UnownedPointer because
757  // a Widget may own Filament resources, so we cannot have Python holding
758  // on to a shared_ptr after we cleanup Filament. The object is initially
759  // "leaked" (as in, Python will not clean it up), but it will be claimed
760  // by the object it is added to. There are two consequences to this:
761  // 1) adding an object to multiple objects will cause multiple shared_ptrs
762  // to think they own it, leading to a double-free and crash, and
763  // 2) if the object is never added, the memory will be leaked.
764  py::class_<Widget, UnownedPointer<Widget>> widget(m_gui, "Widget",
765  "Base widget class");
766 
767  py::native_enum<EventCallbackResult> widget_event_callback_result(
768  widget, "EventCallbackResult", "enum.IntEnum",
769  "Returned by event handlers.");
770  widget_event_callback_result
771  .value("IGNORED", EventCallbackResult::IGNORED,
772  "Event handler ignored the event, widget will "
773  "handle event normally")
774  .value("HANDLED", EventCallbackResult::HANDLED,
775  "Event handler handled the event, but widget "
776  "will still handle the event normally. This is "
777  "useful when you are augmenting base "
778  "functionality")
779  .value("CONSUMED", EventCallbackResult::CONSUMED,
780  "Event handler consumed the event, event "
781  "handling stops, widget will not handle the "
782  "event. This is useful when you are replacing "
783  "functionality")
784  .export_values()
785  .finalize();
786 
787  py::class_<Widget::Constraints> constraints(
788  widget, "Constraints",
789  "Constraints object for Widget.calc_preferred_size()");
790  constraints.def(py::init<>())
791  .def_readwrite("width", &Widget::Constraints::width)
792  .def_readwrite("height", &Widget::Constraints::height);
793 
794  widget.def(py::init<>())
795  .def("__repr__",
796  [](const Widget &w) {
797  std::stringstream s;
798  s << "Widget (" << w.GetFrame().x << ", " << w.GetFrame().y
799  << "), " << w.GetFrame().width << " x "
800  << w.GetFrame().height;
801  return s.str();
802  })
803  .def(
804  "add_child",
805  [](Widget &w, UnownedPointer<Widget> child) {
806  w.AddChild(TakeOwnership<Widget>(child));
807  },
808  "Adds a child widget")
809  .def("get_children", &Widget::GetChildren,
810  "Returns the array of children. Do not modify.")
811  .def_property("frame", &Widget::GetFrame, &Widget::SetFrame,
812  "The widget's frame. Setting this value will be "
813  "overridden if the frame is within a layout.")
814  .def_property("visible", &Widget::IsVisible, &Widget::SetVisible,
815  "True if widget is visible, False otherwise")
816  .def_property("enabled", &Widget::IsEnabled, &Widget::SetEnabled,
817  "True if widget is enabled, False if disabled")
818  .def_property("background_color", &Widget::GetBackgroundColor,
820  "Background color of the widget")
821  .def_property("tooltip", &Widget::GetTooltip, &Widget::SetTooltip,
822  "Widget's tooltip that is displayed on mouseover")
823  .def("calc_preferred_size", &Widget::CalcPreferredSize,
824  "Returns the preferred size of the widget. This is intended "
825  "to be called only during layout, although it will also work "
826  "during drawing. Calling it at other times will not work, as "
827  "it requires some internal setup in order to function "
828  "properly");
829 
830  // ---- WidgetProxy ----
831  py::class_<WidgetProxy, UnownedPointer<WidgetProxy>, Widget> widgetProxy(
832  m_gui, "WidgetProxy",
833  "Widget container to delegate any widget dynamically."
834  " Widget can not be managed dynamically. Although it is allowed"
835  " to add more child widgets, it's impossible to replace some child"
836  " with new on or remove children. WidgetProxy is designed to solve"
837  " this problem."
838  " When WidgetProxy is created, it's invisible and disabled, so it"
839  " won't be drawn or layout, seeming like it does not exist. When"
840  " a widget is set by set_widget, all Widget's APIs will be"
841  " conducted to that child widget. It looks like WidgetProxy is"
842  " that widget."
843  " At any time, a new widget could be set, to replace the old one."
844  " and the old widget will be destroyed."
845  " Due to the content changing after a new widget is set or cleared,"
846  " a relayout of Window might be called after set_widget."
847  " The delegated widget could be retrieved by get_widget in case"
848  " you need to access it directly, like get check status of a"
849  " CheckBox. API other than set_widget and get_widget has"
850  " completely same functions as Widget.");
851  widgetProxy.def(py::init<>(), "Creates a widget proxy")
852  .def("__repr__",
853  [](const WidgetProxy &c) {
854  std::stringstream s;
855  s << "Proxy (" << c.GetFrame().x << ", " << c.GetFrame().y
856  << "), " << c.GetFrame().width << " x "
857  << c.GetFrame().height;
858  return s.str();
859  })
860  .def(
861  "set_widget",
862  [](WidgetProxy &w, UnownedPointer<Widget> proxy) {
863  w.SetWidget(TakeOwnership<Widget>(proxy));
864  },
865  "set a new widget to be delegated by this one."
866  " After set_widget, the previously delegated widget ,"
867  " will be abandon all calls to Widget's API will be "
868  " conducted to widget. Before any set_widget call, "
869  " this widget is invisible and disabled, seems it "
870  " does not exist because it won't be drawn or in a "
871  "layout.")
872  .def("get_widget", &WidgetProxy::GetWidget,
873  "Retrieve current delegated widget."
874  "return instance of current delegated widget set by "
875  "set_widget. An empty pointer will be returned "
876  "if there is none.");
877 
878  // ---- WidgetStack ----
879  py::class_<WidgetStack, UnownedPointer<WidgetStack>, WidgetProxy>
880  widget_stack(m_gui, "WidgetStack",
881  "A widget stack saves all widgets pushed into by "
882  "push_widget and always shows the top one. The "
883  "WidgetStack is a subclass of WidgetProxy, in other"
884  "words, the topmost widget will delegate itself to "
885  "WidgetStack. pop_widget will remove the topmost "
886  "widget and callback set by set_on_top taking the "
887  "new topmost widget will be called. The WidgetStack "
888  "disappears in GUI if there is no widget in stack.");
889  widget_stack
890  .def(py::init<>(),
891  "Creates a widget stack. The widget stack without any"
892  "widget will not be shown in GUI until set_widget is"
893  "called to push a widget.")
894  .def("__repr__",
895  [](const WidgetStack &c) {
896  std::stringstream s;
897  s << "Stack (" << c.GetFrame().x << ", " << c.GetFrame().y
898  << "), " << c.GetFrame().width << " x "
899  << c.GetFrame().height;
900  return s.str();
901  })
902  .def("push_widget", &WidgetStack::PushWidget,
903  "push a new widget onto the WidgetStack's stack, hiding "
904  "whatever widget was there before and making the new widget "
905  "visible.")
906  .def("pop_widget", &WidgetStack::PopWidget,
907  "pop the topmost widget in the stack. The new topmost widget"
908  "of stack will be the widget on the show in GUI.")
909  .def("set_on_top", &WidgetStack::SetOnTop,
910  "Callable[[widget] -> None], called while a widget "
911  "becomes the topmost of stack after some widget is popped"
912  "out. It won't be called if a widget is pushed into stack"
913  "by set_widget.");
914 
915  // ---- Button ----
916  py::class_<Button, UnownedPointer<Button>, Widget> button(m_gui, "Button",
917  "Button");
918  button.def(py::init<const char *>(), "Creates a button with the given text")
919  .def("__repr__",
920  [](const Button &b) {
921  std::stringstream s;
922  s << "Button (" << b.GetFrame().x << ", " << b.GetFrame().y
923  << "), " << b.GetFrame().width << " x "
924  << b.GetFrame().height;
925  return s.str();
926  })
927  .def_property("text", &Button::GetText, &Button::SetText,
928  "Gets/sets the button text.")
929  .def_property(
930  "toggleable", &Button::GetIsToggleable,
932  "True if button is toggleable, False if a push button")
933  .def_property(
934  "is_on", &Button::GetIsOn, &Button::SetOn,
935  "True if the button is toggleable and in the on state")
936  // It is not possible to overload properties. But we want users
937  // to be able to say "o.padding = 1.4" or "o.padding = 1",
938  // and float and int are different types. Fortunately, we want
939  // a float, which is easily castable from int. So we can pass
940  // a py::object and cast it ourselves.
941  .def_property(
942  "horizontal_padding_em", &Button::GetHorizontalPaddingEm,
943  [](UnownedPointer<Button> b, const py::object &em) {
944  auto vert = b->GetVerticalPaddingEm();
945  try {
946  b->SetPaddingEm(em.cast<float>(), vert);
947  } catch (const py::cast_error &) {
948  py::print(
949  "cloudViewer.visualization.gui.Button."
950  "horizontal_padding_em can only be "
951  "assigned a numeric type");
952  }
953  },
954  "Horizontal padding in em units")
955  .def_property(
956  "vertical_padding_em", &Button::GetVerticalPaddingEm,
957  [](UnownedPointer<Button> b, const py::object &em) {
958  auto horiz = b->GetHorizontalPaddingEm();
959  try {
960  b->SetPaddingEm(horiz, em.cast<float>());
961  } catch (const py::cast_error &) {
962  py::print(
963  "cloudViewer.visualization.gui.Button."
964  "vertical_padding_em can only be "
965  "assigned a numeric type");
966  }
967  },
968  "Vertical padding in em units")
969  .def("set_on_clicked", &Button::SetOnClicked,
970  "Calls passed function when button is pressed");
971 
972  // ---- Checkbox ----
973  py::class_<Checkbox, UnownedPointer<Checkbox>, Widget> checkbox(
974  m_gui, "Checkbox", "Checkbox");
975  checkbox.def(py::init<const char *>(),
976  "Creates a checkbox with the given text")
977  .def("__repr__",
978  [](const Checkbox &c) {
979  std::stringstream s;
980  s << "Checkbox (" << c.GetFrame().x << ", "
981  << c.GetFrame().y << "), " << c.GetFrame().width << " x "
982  << c.GetFrame().height;
983  return s.str();
984  })
985  .def_property("checked", &Checkbox::IsChecked,
987  "True if checked, False otherwise")
988  .def("set_on_checked", &Checkbox::SetOnChecked,
989  "Calls passed function when checkbox changes state");
990 
991  // ---- ColorEdit ----
992  py::class_<ColorEdit, UnownedPointer<ColorEdit>, Widget> coloredit(
993  m_gui, "ColorEdit", "Color picker");
994  coloredit.def(py::init<>())
995  .def("__repr__",
996  [](const ColorEdit &c) {
997  auto &color = c.GetValue();
998  std::stringstream s;
999  s << "ColorEdit [" << color.GetRed() << ", "
1000  << color.GetGreen() << ", " << color.GetBlue() << ", "
1001  << color.GetAlpha() << "] (" << c.GetFrame().x << ", "
1002  << c.GetFrame().y << "), " << c.GetFrame().width << " x "
1003  << c.GetFrame().height;
1004  return s.str();
1005  })
1006  .def_property(
1007  "color_value", &ColorEdit::GetValue,
1008  (void(ColorEdit::*)(const Color &)) & ColorEdit::SetValue,
1009  "Color value (gui.Color)")
1010  .def("set_on_value_changed", &ColorEdit::SetOnValueChanged,
1011  "Calls f(Color) when color changes by user input");
1012 
1013  // ---- Combobox ----
1014  py::class_<Combobox, UnownedPointer<Combobox>, Widget> combobox(
1015  m_gui, "Combobox", "Exclusive selection from a pull-down menu");
1016  combobox.def(py::init<>(),
1017  "Creates an empty combobox. Use add_item() to add items")
1018  .def("clear_items", &Combobox::ClearItems, "Removes all items")
1019  .def("add_item", &Combobox::AddItem, "Adds an item to the end")
1020  .def("change_item",
1021  (void(Combobox::*)(int, const char *)) & Combobox::ChangeItem,
1022  "Changes the text of the item at index: "
1023  "change_item(index, newtext)")
1024  .def("change_item",
1025  (void(Combobox::*)(const char *, const char *)) &
1027  "Changes the text of the matching item: "
1028  "change_item(text, newtext)")
1029  .def("remove_item",
1030  (void(Combobox::*)(const char *)) & Combobox::RemoveItem,
1031  "Removes the first item of the given text")
1032  .def("remove_item", (void(Combobox::*)(int)) & Combobox::RemoveItem,
1033  "Removes the item at the index")
1034  .def("get_item", &Combobox::GetItem, "index"_a,
1035  "Returns the item at the given index. Index must be valid.")
1036  .def_property_readonly("number_of_items",
1038  "The number of items (read-only)")
1039  .def_property("selected_index", &Combobox::GetSelectedIndex,
1041  "The index of the currently selected item")
1042  .def_property("selected_text", &Combobox::GetSelectedValue,
1044  "The index of the currently selected item")
1045  .def("set_on_selection_changed", &Combobox::SetOnValueChanged,
1046  "Calls f(str, int) when user selects item from combobox. "
1047  "Arguments are the selected text and selected index, "
1048  "respectively");
1049 
1050  // ---- RadioButton ----
1051  py::class_<RadioButton, UnownedPointer<RadioButton>, Widget> radiobtn(
1052  m_gui, "RadioButton", "Exclusive selection from radio button list");
1053  py::native_enum<RadioButton::Type>(radiobtn, "Type", "enum.Enum",
1054  "Enum class for RadioButton types.")
1055  .value("VERT", RadioButton::Type::VERT)
1056  .value("HORIZ", RadioButton::Type::HORIZ)
1057  .export_values()
1058  .finalize();
1059  radiobtn.def(py::init<RadioButton::Type>(),
1060  "Creates an empty radio buttons. Use set_items() to add items")
1061  .def("set_items", &RadioButton::SetItems,
1062  "Set radio items, each item is a radio button.")
1063  .def_property("selected_index", &RadioButton::GetSelectedIndex,
1065  "The index of the currently selected item")
1066  .def_property_readonly("selected_value",
1068  "The text of the currently selected item")
1069  .def("set_on_selection_changed",
1071  "Calls f(new_idx) when user changes selection");
1072 
1073  // ---- ImageWidget ----
1074  py::class_<UIImage, UnownedPointer<UIImage>> uiimage(
1075  m_gui, "UIImage",
1076  "A bitmap suitable for displaying with ImageWidget");
1077 
1078  py::native_enum<UIImage::Scaling> uiimage_scaling(
1079  uiimage, "Scaling", "enum.Enum", "UIImage::Scaling.");
1080  uiimage_scaling.value("NONE", UIImage::Scaling::NONE)
1081  .value("ANY", UIImage::Scaling::ANY)
1082  .value("ASPECT", UIImage::Scaling::ASPECT)
1083  .finalize();
1084 
1085  uiimage.def(py::init<>([](const char *path) { return new UIImage(path); }),
1086  "Creates a UIImage from the image at the specified path")
1087  .def(py::init<>([](std::shared_ptr<geometry::Image> image) {
1088  return new UIImage(image);
1089  }),
1090  "Creates a UIImage from the provided image")
1091  .def("__repr__", [](const UIImage &il) { return "UIImage"; })
1092  .def_property("scaling", &UIImage::GetScaling, &UIImage::SetScaling,
1093  "Sets how the image is scaled:\n"
1094  "gui.UIImage.Scaling.NONE: no scaling\n"
1095  "gui.UIImage.Scaling.ANY: scaled to fit\n"
1096  "gui.UIImage.Scaling.ASPECT: scaled to fit but "
1097  "keeping the image's aspect ratio");
1098  py::class_<PyImageWidget, UnownedPointer<PyImageWidget>, Widget>
1099  imagewidget(m_gui, "ImageWidget", "Displays a bitmap");
1100  imagewidget
1101  .def(py::init<>([]() { return new PyImageWidget(); }),
1102  "Creates an ImageWidget with no image")
1103  .def(py::init<>([](const char *path) {
1104  return new PyImageWidget(path);
1105  }),
1106  "Creates an ImageWidget from the image at the specified path")
1107  .def(py::init<>([](std::shared_ptr<geometry::Image> image) {
1108  return new PyImageWidget(image);
1109  }),
1110  "Creates an ImageWidget from the provided image")
1111  .def(py::init<>([](std::shared_ptr<t::geometry::Image> image) {
1112  return new PyImageWidget(image);
1113  }),
1114  "Creates an ImageWidget from the provided tgeometry image")
1115  .def("__repr__",
1116  [](const PyImageWidget &il) {
1117  std::stringstream s;
1118  s << "ImageWidget (" << il.GetFrame().x << ", "
1119  << il.GetFrame().y << "), " << il.GetFrame().width
1120  << " x " << il.GetFrame().height;
1121  return s.str();
1122  })
1123  .def("update_image",
1124  py::overload_cast<std::shared_ptr<geometry::Image>>(
1126  "Mostly a convenience function for ui_image.update_image(). "
1127  "If 'image' is the same size as the current image, will "
1128  "update the texture with the contents of 'image'. This is "
1129  "the fastest path for setting an image, and is recommended "
1130  "if you are displaying video. If 'image' is a different size, "
1131  "it will allocate a new texture, which is essentially the "
1132  "same as creating a new UIImage and calling SetUIImage(). "
1133  "This is the slow path, and may eventually exhaust internal "
1134  "texture resources.")
1135  .def("update_image",
1136  py::overload_cast<std::shared_ptr<t::geometry::Image>>(
1138  "Mostly a convenience function for ui_image.update_image(). "
1139  "If 'image' is the same size as the current image, will "
1140  "update the texture with the contents of 'image'. This is "
1141  "the fastest path for setting an image, and is recommended "
1142  "if you are displaying video. If 'image' is a different size, "
1143  "it will allocate a new texture, which is essentially the "
1144  "same as creating a new UIImage and calling SetUIImage(). "
1145  "This is the slow path, and may eventually exhaust internal "
1146  "texture resources.")
1147  .def("set_on_mouse", &PyImageWidget::SetOnMouse,
1148  "Sets a callback for mouse events. This callback is passed "
1149  "a MouseEvent object. The callback must return "
1150  "EventCallbackResult.IGNORED, EventCallbackResult.HANDLED, "
1151  "or EventCallbackResult.CONSUMED.")
1152  .def("set_on_key", &PyImageWidget::SetOnKey,
1153  "Sets a callback for key events. This callback is passed "
1154  "a KeyEvent object. The callback must return "
1155  "EventCallbackResult.IGNORED, EventCallbackResult.HANDLED, "
1156  "or EventCallackResult.CONSUMED.")
1157  .def_property("ui_image", &PyImageWidget::GetUIImage,
1159  "Replaces the texture with a new texture. This is "
1160  "not a fast path, and is not recommended for video "
1161  "as you will exhaust internal texture resources.");
1162 
1163  // ---- Label ----
1164  py::class_<Label, UnownedPointer<Label>, Widget> label(m_gui, "Label",
1165  "Displays text");
1166  label.def(py::init([](const char *title = "") { return new Label(title); }),
1167  "Creates a Label with the given text")
1168  .def("__repr__",
1169  [](const Label &lbl) {
1170  std::stringstream s;
1171  s << "Label [" << lbl.GetText() << "] ("
1172  << lbl.GetFrame().x << ", " << lbl.GetFrame().y << "), "
1173  << lbl.GetFrame().width << " x "
1174  << lbl.GetFrame().height;
1175  return s.str();
1176  })
1177  .def_property("text", &Label::GetText, &Label::SetText,
1178  "The text of the label. Newlines will be treated as "
1179  "line breaks")
1180  .def_property("text_color", &Label::GetTextColor,
1182  "The color of the text (gui.Color)")
1183  .def_property("font_id", &Label::GetFontId, &Label::SetFontId,
1184  "Set the font using the FontId returned from "
1185  "Application.add_font()");
1186 
1187  // ---- Label3D ----
1188  py::class_<Label3D, UnownedPointer<Label3D>> label3d(
1189  m_gui, "Label3D", "Displays text in a 3D scene");
1190  label3d.def(py::init([](const char *text = "",
1191  const Eigen::Vector3f &pos = {0.f, 0.f, 0.f}) {
1192  return new Label3D(pos, text);
1193  }),
1194  "Create a 3D Label with given text and position")
1195  .def_property("text", &Label3D::GetText, &Label3D::SetText,
1196  "The text to display with this label.")
1197  .def_property("position", &Label3D::GetPosition,
1199  "The position of the text in 3D coordinates")
1200  .def_property("color", &Label3D::GetTextColor,
1202  "The color of the text (gui.Color)");
1203 
1204  // ---- ListView ----
1205  py::class_<ListView, UnownedPointer<ListView>, Widget> listview(
1206  m_gui, "ListView", "Displays a list of text");
1207  listview.def(py::init<>(), "Creates an empty list")
1208  .def("__repr__",
1209  [](const ListView &lv) {
1210  std::stringstream s;
1211  s << "Label (" << lv.GetFrame().x << ", "
1212  << lv.GetFrame().y << "), " << lv.GetFrame().width
1213  << " x " << lv.GetFrame().height;
1214  return s.str();
1215  })
1216  .def("set_items", &ListView::SetItems,
1217  "Sets the list to display the list of items provided")
1218  .def("set_max_visible_items", &ListView::SetMaxVisibleItems,
1219  "Limit the max visible items shown to user. "
1220  "Set to negative number will make list extends vertically "
1221  "as much as possible, otherwise the list will at least show "
1222  "3 items and at most show num items.")
1223  .def_property("selected_index", &ListView::GetSelectedIndex,
1225  "The index of the currently selected item")
1226  .def_property_readonly("selected_value",
1228  "The text of the currently selected item")
1229  .def("set_on_selection_changed", &ListView::SetOnValueChanged,
1230  "Calls f(new_val, is_double_click) when user changes "
1231  "selection");
1232 
1233  // ---- NumberEdit ----
1234  py::class_<NumberEdit, UnownedPointer<NumberEdit>, Widget> numedit(
1235  m_gui, "NumberEdit", "Allows the user to enter a number.");
1236 
1237  py::native_enum<NumberEdit::Type> numedit_type(
1238  numedit, "Type", "enum.Enum", "Enum class for NumberEdit types.");
1239  numedit_type.value("INT", NumberEdit::Type::INT)
1240  .value("DOUBLE", NumberEdit::Type::DOUBLE)
1241  .export_values()
1242  .finalize();
1243 
1244  numedit.def(py::init<NumberEdit::Type>(),
1245  "Creates a NumberEdit that is either integers (INT) or "
1246  "floating point (DOUBLE). The initial value is 0 and the "
1247  "limits are +/- max integer (roughly).")
1248  .def("__repr__",
1249  [](const NumberEdit &ne) {
1250  auto val = ne.GetDoubleValue();
1251  std::stringstream s;
1252  s << "NumberEdit [" << val << "] (" << ne.GetFrame().x
1253  << ", " << ne.GetFrame().y << "), "
1254  << ne.GetFrame().width << " x " << ne.GetFrame().height;
1255  return s.str();
1256  })
1257  .def_property(
1258  "int_value", &NumberEdit::GetIntValue,
1259  [](UnownedPointer<NumberEdit> ne, int val) {
1260  ne->SetValue(double(val));
1261  },
1262  "Current value (int)")
1263  .def_property("double_value", &NumberEdit::GetDoubleValue,
1264  &NumberEdit::SetValue, "Current value (double)")
1265  .def("set_value", &NumberEdit::SetValue, "Sets value")
1266  .def_property("decimal_precision", &NumberEdit::GetDecimalPrecision,
1268  "Number of fractional digits shown")
1269  .def_property_readonly("minimum_value",
1271  "The minimum value number can contain "
1272  "(read-only, use set_limits() to set)")
1273  .def_property_readonly("maximum_value",
1275  "The maximum value number can contain "
1276  "(read-only, use set_limits() to set)")
1277  .def("set_limits", &NumberEdit::SetLimits,
1278  "Sets the minimum and maximum values for the number")
1279  .def("set_on_value_changed", &NumberEdit::SetOnValueChanged,
1280  "Sets f(new_value) which is called with a Float when user "
1281  "changes widget's value")
1282  .def("set_preferred_width", &NumberEdit::SetPreferredWidth,
1283  "Sets the preferred width of the NumberEdit")
1284  .def(
1285  "set_preferred_width",
1286  [](NumberEdit &ne, double width) {
1287  ne.NumberEdit::SetPreferredWidth(int(width));
1288  },
1289  "Sets the preferred width of the NumberEdit");
1290 
1291  // ---- ProgressBar----
1292  py::class_<ProgressBar, UnownedPointer<ProgressBar>, Widget> progress(
1293  m_gui, "ProgressBar", "Displays a progress bar");
1294  progress.def(py::init<>())
1295  .def("__repr__",
1296  [](const ProgressBar &pb) {
1297  std::stringstream s;
1298  s << "ProgressBar [" << pb.GetValue() << "] ("
1299  << pb.GetFrame().x << ", " << pb.GetFrame().y << "), "
1300  << pb.GetFrame().width << " x " << pb.GetFrame().height;
1301  return s.str();
1302  })
1303  .def_property(
1305  "The value of the progress bar, ranges from 0.0 to 1.0");
1306 
1307  // ---- SceneWidget ----
1308  class PySceneWidget : public SceneWidget {
1309  using Super = SceneWidget;
1310 
1311  public:
1312  void SetOnMouse(std::function<int(const MouseEvent &)> f) {
1313  on_mouse_ = f;
1314  }
1315  void SetOnKey(std::function<int(const KeyEvent &)> f) { on_key_ = f; }
1316 
1317  Widget::EventResult Mouse(const MouseEvent &e) override {
1318  if (on_mouse_) {
1319  switch (EventCallbackResult(on_mouse_(e))) {
1323  auto result = Super::Mouse(e);
1326  }
1327  return result;
1328  }
1330  default:
1331  return Super::Mouse(e);
1332  }
1333  } else {
1334  return Super::Mouse(e);
1335  }
1336  }
1337 
1338  Widget::EventResult Key(const KeyEvent &e) override {
1339  if (on_key_) {
1340  switch (EventCallbackResult(on_key_(e))) {
1344  auto result = Super::Key(e);
1347  }
1348  return result;
1349  }
1351  default:
1352  return Super::Key(e);
1353  }
1354  } else {
1355  return Super::Key(e);
1356  }
1357  }
1358 
1359  private:
1360  std::function<int(const MouseEvent &)> on_mouse_;
1361  std::function<int(const KeyEvent &)> on_key_;
1362  };
1363 
1364  py::class_<PySceneWidget, UnownedPointer<PySceneWidget>, Widget> scene(
1365  m_gui, "SceneWidget", "Displays 3D content");
1366 
1367  py::native_enum<SceneWidget::Controls> scene_ctrl(
1368  scene, "Controls", "enum.Enum",
1369  "Enum class describing mouse interaction.");
1370  scene_ctrl.value("ROTATE_CAMERA", SceneWidget::Controls::ROTATE_CAMERA)
1371  .value("ROTATE_CAMERA_SPHERE",
1372  SceneWidget::Controls::ROTATE_CAMERA_SPHERE)
1373  .value("FLY", SceneWidget::Controls::FLY)
1374  .value("ROTATE_SUN", SceneWidget::Controls::ROTATE_SUN)
1375  .value("ROTATE_IBL", SceneWidget::Controls::ROTATE_IBL)
1376  .value("ROTATE_MODEL", SceneWidget::Controls::ROTATE_MODEL)
1377  .value("PICK_POINTS", SceneWidget::Controls::PICK_POINTS)
1378  .export_values()
1379  .finalize();
1380 
1381  scene.def(py::init<>(),
1382  "Creates an empty SceneWidget. Assign a Scene with the 'scene' "
1383  "property")
1384  .def_property("scene", &PySceneWidget::GetScene,
1386  "The rendering.CloudViewerScene that the SceneWidget "
1387  "renders")
1388  .def_property("center_of_rotation",
1391  "Current center of rotation (for ROTATE_CAMERA and "
1392  "ROTATE_CAMERA_SPHERE)")
1393  .def("enable_scene_caching", &PySceneWidget::EnableSceneCaching,
1394  "Enable/Disable caching of scene content when the view or "
1395  "model is not changing. Scene caching can help improve UI "
1396  "responsiveness for large models and point clouds")
1397  .def("force_redraw", &PySceneWidget::ForceRedraw,
1398  "Ensures scene redraws even when scene caching is enabled.")
1399  .def("set_view_controls", &PySceneWidget::SetViewControls,
1400  "Sets mouse interaction, e.g. ROTATE_OBJ")
1401  .def("setup_camera",
1402  py::overload_cast<float, const ccBBox &,
1403  const Eigen::Vector3f &>(
1405  "Configure the camera: setup_camera(field_of_view, "
1406  "model_bounds, center_of_rotation)")
1407  .def("setup_camera",
1408  py::overload_cast<const camera::PinholeCameraIntrinsic &,
1409  const Eigen::Matrix4d &, const ccBBox &>(
1411  "setup_camera(intrinsics, extrinsic_matrix, model_bounds): "
1412  "sets the camera view")
1413  .def("setup_camera",
1414  py::overload_cast<const Eigen::Matrix3d &,
1415  const Eigen::Matrix4d &, int, int,
1417  "setup_camera(intrinsic_matrix, extrinsic_matrix, "
1418  "intrinsic_width_px, intrinsic_height_px, model_bounds): "
1419  "sets the camera view")
1420  .def("look_at", &PySceneWidget::LookAt,
1421  "look_at(center, eye, up): sets the "
1422  "camera view so that the camera is located at 'eye', pointing "
1423  "towards 'center', and oriented so that the up vector is 'up'")
1424  .def("set_on_mouse", &PySceneWidget::SetOnMouse,
1425  "Sets a callback for mouse events. This callback is passed "
1426  "a MouseEvent object. The callback must return "
1427  "EventCallbackResult.IGNORED, EventCallbackResult.HANDLED, "
1428  "or EventCallackResult.CONSUMED.")
1429  .def("set_on_key", &PySceneWidget::SetOnKey,
1430  "Sets a callback for key events. This callback is passed "
1431  "a KeyEvent object. The callback must return "
1432  "EventCallbackResult.IGNORED, EventCallbackResult.HANDLED, "
1433  "or EventCallackResult.CONSUMED.")
1434  .def("set_on_sun_direction_changed",
1436  "Callback when user changes sun direction (only called in "
1437  "ROTATE_SUN control mode). Called with one argument, the "
1438  "[i, j, k] vector of the new sun direction")
1439  .def("add_3d_label", &PySceneWidget::AddLabel,
1440  "Add a 3D text label to the scene. The label will be anchored "
1441  "at the specified 3D point.")
1442  .def("remove_3d_label", &PySceneWidget::RemoveLabel,
1443  "Removes the 3D text label from the scene");
1444 
1445  // ---- Slider ----
1446  py::class_<Slider, UnownedPointer<Slider>, Widget> slider(
1447  m_gui, "Slider", "A slider widget for visually selecting numbers");
1448 
1449  py::native_enum<Slider::Type> slider_type(slider, "Type", "enum.Enum",
1450  "Enum class for Slider types.");
1451  slider_type.value("INT", Slider::Type::INT)
1452  .value("DOUBLE", Slider::Type::DOUBLE)
1453  .export_values()
1454  .finalize();
1455 
1456  slider.def(py::init<Slider::Type>(),
1457  "Creates a NumberEdit that is either integers (INT) or "
1458  "floating point (DOUBLE). The initial value is 0 and the limits "
1459  "are +/- infinity.")
1460  .def("__repr__",
1461  [](const Slider &sl) {
1462  auto val = sl.GetDoubleValue();
1463  std::stringstream s;
1464  s << "TextEdit [" << val << "] (" << sl.GetFrame().x
1465  << ", " << sl.GetFrame().y << "), "
1466  << sl.GetFrame().width << " x " << sl.GetFrame().height;
1467  return s.str();
1468  })
1469  .def_property(
1470  "int_value", &Slider::GetIntValue,
1471  [](UnownedPointer<Slider> ne, int val) {
1472  ne->SetValue(double(val));
1473  },
1474  "Slider value (int)")
1475  .def_property("double_value", &Slider::GetDoubleValue,
1476  &Slider::SetValue, "Slider value (double)")
1477  .def_property_readonly("get_minimum_value",
1479  "The minimum value number can contain "
1480  "(read-only, use set_limits() to set)")
1481  .def_property_readonly("get_maximum_value",
1483  "The maximum value number can contain "
1484  "(read-only, use set_limits() to set)")
1485  .def("set_limits", &Slider::SetLimits,
1486  "Sets the minimum and maximum values for the slider")
1487  .def("set_on_value_changed", &Slider::SetOnValueChanged,
1488  "Sets f(new_value) which is called with a Float when user "
1489  "changes widget's value");
1490 
1491  // ---- StackedWidget ----
1492  py::class_<StackedWidget, UnownedPointer<StackedWidget>, Widget> stacked(
1493  m_gui, "StackedWidget", "Like a TabControl but without the tabs");
1494  stacked.def(py::init<>())
1495  .def_property("selected_index", &StackedWidget::GetSelectedIndex,
1497  "Selects the index of the child to display");
1498 
1499  // ---- TabControl ----
1500  py::class_<TabControl, UnownedPointer<TabControl>, Widget> tabctrl(
1501  m_gui, "TabControl", "Tab control");
1502  tabctrl.def(py::init<>())
1503  .def(
1504  "add_tab",
1505  [](TabControl &tabs, const char *name,
1506  UnownedPointer<Widget> panel) {
1507  tabs.AddTab(name, TakeOwnership<Widget>(panel));
1508  },
1509  "Adds a tab. The first parameter is the title of the tab, "
1510  "and the second parameter is a widget--normally this is a "
1511  "layout.")
1512  .def("set_on_selected_tab_changed",
1514  "Calls the provided callback function with the index of the "
1515  "currently selected tab whenever the user clicks on a "
1516  "different tab");
1517 
1518  // ---- TextEdit ----
1519  py::class_<TextEdit, UnownedPointer<TextEdit>, Widget> textedit(
1520  m_gui, "TextEdit", "Allows the user to enter or modify text");
1521  textedit.def(py::init<>(),
1522  "Creates a TextEdit widget with an initial value of an empty "
1523  "string.")
1524  .def("__repr__",
1525  [](const TextEdit &te) {
1526  auto val = te.GetText();
1527  std::stringstream s;
1528  s << "TextEdit [" << val << "] (" << te.GetFrame().x
1529  << ", " << te.GetFrame().y << "), "
1530  << te.GetFrame().width << " x " << te.GetFrame().height;
1531  return s.str();
1532  })
1533  .def_property("text_value", &TextEdit::GetText, &TextEdit::SetText,
1534  "The value of text")
1535  .def_property(
1536  "placeholder_text", &TextEdit::GetPlaceholderText,
1538  "The placeholder text displayed when text value is empty")
1539  .def("set_on_text_changed", &TextEdit::SetOnTextChanged,
1540  "Sets f(new_text) which is called whenever the the user makes "
1541  "a change to the text")
1542  .def("set_on_value_changed", &TextEdit::SetOnValueChanged,
1543  "Sets f(new_text) which is called with the new text when the "
1544  "user completes text editing");
1545 
1546  // ---- ToggleSwitch ----
1547  py::class_<ToggleSwitch, UnownedPointer<ToggleSwitch>, Widget> toggle(
1548  m_gui, "ToggleSwitch", "ToggleSwitch");
1549  toggle.def(py::init<const char *>(),
1550  "Creates a toggle switch with the given text")
1551  .def("__repr__",
1552  [](const ToggleSwitch &ts) {
1553  std::stringstream s;
1554  s << "ToggleSwitch (" << ts.GetFrame().x << ", "
1555  << ts.GetFrame().y << "), " << ts.GetFrame().width
1556  << " x " << ts.GetFrame().height;
1557  return s.str();
1558  })
1559  .def_property("is_on", &ToggleSwitch::GetIsOn, &ToggleSwitch::SetOn,
1560  "True if is one, False otherwise")
1561  .def("set_on_clicked", &ToggleSwitch::SetOnClicked,
1562  "Sets f(is_on) which is called when the switch changes "
1563  "state.");
1564 
1565  // ---- TreeView ----
1566  py::class_<TreeView, UnownedPointer<TreeView>, Widget> treeview(
1567  m_gui, "TreeView", "Hierarchical list");
1568  treeview.def(py::init<>(), "Creates an empty TreeView widget")
1569  .def("__repr__",
1570  [](const TreeView &tv) {
1571  std::stringstream s;
1572  s << "TreeView (" << tv.GetFrame().x << ", "
1573  << tv.GetFrame().y << "), " << tv.GetFrame().width
1574  << " x " << tv.GetFrame().height;
1575  return s.str();
1576  })
1577  .def("get_root_item", &TreeView::GetRootItem,
1578  "Returns the root item. This item is invisible, so its child "
1579  "are the top-level items")
1580  .def(
1581  "add_item",
1582  [](TreeView &tree, TreeView::ItemId parent_id,
1583  UnownedPointer<Widget> item) {
1584  return tree.AddItem(parent_id,
1585  TakeOwnership<Widget>(item));
1586  },
1587  "Adds a child item to the parent. add_item(parent, widget)")
1588  .def("add_text_item", &TreeView::AddTextItem,
1589  "Adds a child item to the parent. add_text_item(parent, text)")
1590  .def("remove_item", &TreeView::RemoveItem,
1591  "Removes an item and all its children (if any)")
1592  .def("clear", &TreeView::Clear, "Removes all items")
1593  .def("get_item", &TreeView::GetItem, "item_id"_a,
1594  "Returns the widget associated with the provided Item ID. For "
1595  "example, to manipulate the widget of the currently selected "
1596  "item you would use the ItemID of the selected_item property "
1597  "with get_item to get the widget.")
1598  .def_property(
1599  "can_select_items_with_children",
1602  "If set to False, clicking anywhere on an item with "
1603  "will toggle the item open or closed; the item cannot be "
1604  "selected. If set to True, items with children can be "
1605  "selected, and to toggle open/closed requires clicking "
1606  "the arrow or double-clicking the item")
1607  .def_property("selected_item", &TreeView::GetSelectedItemId,
1609  "The currently selected item")
1610  .def("set_on_selection_changed", &TreeView::SetOnSelectionChanged,
1611  "Sets f(new_item_id) which is called when the user "
1612  "changes the selection.");
1613 
1614  // ---- TreeView cells ----
1615  py::class_<CheckableTextTreeCell, UnownedPointer<CheckableTextTreeCell>,
1616  Widget>
1617  checkable_cell(m_gui, "CheckableTextTreeCell",
1618  "TreeView cell with a checkbox and text");
1619  checkable_cell
1620  .def(py::init<>([](const char *text, bool checked,
1621  std::function<void(bool)> on_toggled) {
1622  return new CheckableTextTreeCell(text, checked,
1623  on_toggled);
1624  }),
1625  "Creates a TreeView cell with a checkbox and text. "
1626  "CheckableTextTreeCell(text, is_checked, on_toggled): "
1627  "on_toggled takes a boolean and returns None")
1628  .def_property_readonly("checkbox",
1630  "Returns the checkbox widget "
1631  "(property is read-only)")
1632  .def_property_readonly("label", &CheckableTextTreeCell::GetLabel,
1633  "Returns the label widget "
1634  "(property is read-only)");
1635 
1636  py::class_<LUTTreeCell, UnownedPointer<LUTTreeCell>, Widget> lut_cell(
1637  m_gui, "LUTTreeCell",
1638  "TreeView cell with checkbox, text, and color edit");
1639  lut_cell.def(py::init<>([](const char *text, bool checked,
1640  const Color &color,
1641  std::function<void(bool)> on_enabled,
1642  std::function<void(const Color &)> on_color) {
1643  return new LUTTreeCell(text, checked, color, on_enabled,
1644  on_color);
1645  }),
1646  "Creates a TreeView cell with a checkbox, text, and "
1647  "a color editor. LUTTreeCell(text, is_checked, color, "
1648  "on_enabled, on_color): on_enabled is called when the "
1649  "checkbox toggles, and takes a boolean and returns None"
1650  "; on_color is called when the user changes the color "
1651  "and it takes a gui.Color and returns None.")
1652  .def_property_readonly("checkbox", &LUTTreeCell::GetCheckbox,
1653  "Returns the checkbox widget "
1654  "(property is read-only)")
1655  .def_property_readonly("label", &LUTTreeCell::GetLabel,
1656  "Returns the label widget "
1657  "(property is read-only)")
1658  .def_property_readonly("color_edit", &LUTTreeCell::GetColorEdit,
1659  "Returns the ColorEdit widget "
1660  "(property is read-only)");
1661 
1662  py::class_<ColormapTreeCell, UnownedPointer<ColormapTreeCell>, Widget>
1663  colormap_cell(m_gui, "ColormapTreeCell",
1664  "TreeView cell with a number edit and color edit");
1665  colormap_cell
1666  .def(py::init<>([](float value, const Color &color,
1667  std::function<void(double)> on_value_changed,
1668  std::function<void(const Color &)>
1669  on_color_changed) {
1670  return new ColormapTreeCell(value, color, on_value_changed,
1671  on_color_changed);
1672  }),
1673  "Creates a TreeView cell with a number and a color edit. "
1674  "ColormapTreeCell(value, color, on_value_changed, "
1675  "on_color_changed): on_value_changed takes a double "
1676  "and returns None; on_color_changed takes a "
1677  "gui.Color and returns None")
1678  .def_property_readonly("number_edit",
1680  "Returns the NumberEdit widget "
1681  "(property is read-only)")
1682  .def_property_readonly("color_edit",
1684  "Returns the ColorEdit widget "
1685  "(property is read-only)");
1686 
1687  // ---- VectorEdit ----
1688  py::class_<VectorEdit, UnownedPointer<VectorEdit>, Widget> vectoredit(
1689  m_gui, "VectorEdit", "Allows the user to edit a 3-space vector");
1690  vectoredit.def(py::init<>())
1691  .def("__repr__",
1692  [](const VectorEdit &ve) {
1693  auto val = ve.GetValue();
1694  std::stringstream s;
1695  s << "VectorEdit [" << val.x() << ", " << val.y() << ", "
1696  << val.z() << "] (" << ve.GetFrame().x << ", "
1697  << ve.GetFrame().y << "), " << ve.GetFrame().width
1698  << " x " << ve.GetFrame().height;
1699  return s.str();
1700  })
1701  .def_property("vector_value", &VectorEdit::GetValue,
1702  &VectorEdit::SetValue, "Returns value [x, y, z]")
1703  .def("set_on_value_changed", &VectorEdit::SetOnValueChanged,
1704  "Sets f([x, y, z]) which is called whenever the user "
1705  "changes the value of a component");
1706 
1707  // ---- Margins ----
1708  py::class_<Margins, UnownedPointer<Margins>> margins(m_gui, "Margins",
1709  "Margins for layouts");
1710  margins.def(py::init([](int left, int top, int right, int bottom) {
1711  return new Margins(left, top, right, bottom);
1712  }),
1713  "left"_a = 0, "top"_a = 0, "right"_a = 0, "bottom"_a = 0,
1714  "Creates margins. Arguments are left, top, right, bottom. "
1715  "Use the em-size (window.theme.font_size) rather than pixels "
1716  "for more consistency across platforms and monitors. Margins "
1717  "are the spacing from the edge of the widget's frame to its "
1718  "content area. They act similar to the 'padding' property in "
1719  "CSS")
1720  .def(py::init([](float left, float top, float right, float bottom) {
1721  return new Margins(
1722  int(std::round(left)), int(std::round(top)),
1723  int(std::round(right)), int(std::round(bottom)));
1724  }),
1725  "left"_a = 0.0f, "top"_a = 0.0f, "right"_a = 0.0f,
1726  "bottom"_a = 0.0f,
1727  "Creates margins. Arguments are left, top, right, bottom. "
1728  "Use the em-size (window.theme.font_size) rather than pixels "
1729  "for more consistency across platforms and monitors. Margins "
1730  "are the spacing from the edge of the widget's frame to its "
1731  "content area. They act similar to the 'padding' property in "
1732  "CSS")
1733  .def_readwrite("left", &Margins::left)
1734  .def_readwrite("top", &Margins::top)
1735  .def_readwrite("right", &Margins::right)
1736  .def_readwrite("bottom", &Margins::bottom)
1737  .def("get_horiz", &Margins::GetHoriz)
1738  .def("get_vert", &Margins::GetVert);
1739 
1740  // ---- Layout1D ----
1741  py::class_<Layout1D, UnownedPointer<Layout1D>, Widget> layout1d(
1742  m_gui, "Layout1D", "Layout base class");
1743  layout1d
1744  // TODO: write the proper constructor
1745  // .def(py::init([]() { return new Layout1D(Layout1D::VERT,
1746  // 0, Margins(), {}); }))
1747  .def("add_fixed", &Layout1D::AddFixed,
1748  "Adds a fixed amount of empty space to the layout")
1749  .def(
1750  "add_fixed",
1751  [](UnownedPointer<Layout1D> layout, float px) {
1752  layout->AddFixed(int(std::round(px)));
1753  },
1754  "Adds a fixed amount of empty space to the layout")
1755  .def("add_stretch", &Layout1D::AddStretch,
1756  "Adds empty space to the layout that will take up as much "
1757  "extra space as there is available in the layout");
1758 
1759  // ---- Vert ----
1760  py::class_<Vert, UnownedPointer<Vert>, Layout1D> vlayout(m_gui, "Vert",
1761  "Vertical layout");
1762  vlayout.def(py::init([](int spacing, const Margins &margins) {
1763  return new Vert(spacing, margins);
1764  }),
1765  "spacing"_a = 0, "margins"_a = Margins(),
1766  "Creates a layout that arranges widgets vertically, top to "
1767  "bottom, making their width equal to the layout's width. First "
1768  "argument is the spacing between widgets, the second is the "
1769  "margins. Both default to 0.")
1770  .def(py::init([](float spacing, const Margins &margins) {
1771  return new Vert(int(std::round(spacing)), margins);
1772  }),
1773  "spacing"_a = 0.0f, "margins"_a = Margins(),
1774  "Creates a layout that arranges widgets vertically, top to "
1775  "bottom, making their width equal to the layout's width. "
1776  "First argument is the spacing between widgets, the second "
1777  "is the margins. Both default to 0.")
1778  .def_property("preferred_width", &Vert::GetPreferredWidth,
1780  "Sets the preferred width of the layout");
1781 
1782  // ---- CollapsableVert ----
1783  py::class_<CollapsableVert, UnownedPointer<CollapsableVert>, Vert>
1784  collapsable(m_gui, "CollapsableVert",
1785  "Vertical layout with title, whose contents are "
1786  "collapsable");
1787  collapsable
1788  .def(py::init([](const char *text, int spacing,
1789  const Margins &margins) {
1790  return new CollapsableVert(text, spacing, margins);
1791  }),
1792  "text"_a, "spacing"_a = 0, "margins"_a = Margins(),
1793  "Creates a layout that arranges widgets vertically, top to "
1794  "bottom, making their width equal to the layout's width. "
1795  "First argument is the heading text, the second is the "
1796  "spacing between widgets, and the third is the margins. "
1797  "Both the spacing and the margins default to 0.")
1798  .def(py::init([](const char *text, float spacing,
1799  const Margins &margins) {
1800  return new CollapsableVert(text, int(std::round(spacing)),
1801  margins);
1802  }),
1803  "text"_a, "spacing"_a = 0.0f, "margins"_a = Margins(),
1804  "Creates a layout that arranges widgets vertically, top to "
1805  "bottom, making their width equal to the layout's width. "
1806  "First argument is the heading text, the second is the "
1807  "spacing between widgets, and the third is the margins. "
1808  "Both the spacing and the margins default to 0.")
1809  .def("set_is_open", &CollapsableVert::SetIsOpen, "is_open"_a,
1810  "Sets to collapsed (False) or open (True). Requires a call to "
1811  "Window.SetNeedsLayout() afterwards, unless calling before "
1812  "window is visible")
1813  .def("get_is_open", &CollapsableVert::GetIsOpen,
1814  "Check if widget is open.")
1815  .def("set_text", &CollapsableVert::SetText, "text"_a,
1816  "Sets the text for the CollapsableVert")
1817  .def("get_text", &CollapsableVert::GetText,
1818  "Gets the text for the CollapsableVert")
1819  .def_property("font_id", &CollapsableVert::GetFontId,
1821  "Set the font using the FontId returned from "
1822  "Application.add_font()");
1823 
1824  // ---- ScrollableVert ----
1825  py::class_<ScrollableVert, UnownedPointer<ScrollableVert>, Vert> slayout(
1826  m_gui, "ScrollableVert", "Scrollable vertical layout");
1827  slayout.def(py::init([](int spacing, const Margins &margins) {
1828  return new ScrollableVert(spacing, margins);
1829  }),
1830  "spacing"_a = 0, "margins"_a = Margins(),
1831  "Creates a layout that arranges widgets vertically, top to "
1832  "bottom, making their width equal to the layout's width. First "
1833  "argument is the spacing between widgets, the second is the "
1834  "margins. Both default to 0.")
1835  .def(py::init([](float spacing, const Margins &margins) {
1836  return new ScrollableVert(int(std::round(spacing)),
1837  margins);
1838  }),
1839  "spacing"_a = 0.0f, "margins"_a = Margins(),
1840  "Creates a layout that arranges widgets vertically, top to "
1841  "bottom, making their width equal to the layout's width. "
1842  "First argument is the spacing between widgets, the second "
1843  "is the margins. Both default to 0.");
1844 
1845  // ---- Horiz ----
1846  py::class_<Horiz, UnownedPointer<Horiz>, Layout1D> hlayout(
1847  m_gui, "Horiz", "Horizontal layout");
1848  hlayout.def(py::init([](int spacing, const Margins &margins) {
1849  return new Horiz(spacing, margins);
1850  }),
1851  "spacing"_a = 0, "margins"_a = Margins(),
1852  "Creates a layout that arranges widgets vertically, left to "
1853  "right, making their height equal to the layout's height "
1854  "(which will generally be the largest height of the items). "
1855  "First argument is the spacing between widgets, the second "
1856  "is the margins. Both default to 0.")
1857  .def(py::init([](float spacing, const Margins &margins) {
1858  return new Horiz(int(std::round(spacing)), margins);
1859  }),
1860  "spacing"_a = 0.0f, "margins"_a = Margins(),
1861  "Creates a layout that arranges widgets vertically, left to "
1862  "right, making their height equal to the layout's height "
1863  "(which will generally be the largest height of the items). "
1864  "First argument is the spacing between widgets, the second "
1865  "is the margins. Both default to 0.")
1866  .def_property("preferred_height", &Horiz::GetPreferredHeight,
1868  "Sets the preferred height of the layout");
1869 
1870  // ---- VGrid ----
1871  py::class_<VGrid, UnownedPointer<VGrid>, Widget> vgrid(m_gui, "VGrid",
1872  "Grid layout");
1873  vgrid.def(py::init([](int n_cols, int spacing, const Margins &margins) {
1874  return new VGrid(n_cols, spacing, margins);
1875  }),
1876  "cols"_a, "spacing"_a = 0, "margins"_a = Margins(),
1877  "Creates a layout that orders its children in a grid, left to "
1878  "right, top to bottom, according to the number of columns. "
1879  "The first argument is the number of columns, the second is the "
1880  "spacing between items (both vertically and horizontally), and "
1881  "third is the margins. Both spacing and margins default to zero.")
1882  .def(py::init(
1883  [](int n_cols, float spacing, const Margins &margins) {
1884  return new VGrid(n_cols, int(std::round(spacing)),
1885  margins);
1886  }),
1887  "cols"_a, "spacing"_a = 0.0f, "margins"_a = Margins(),
1888  "Creates a layout that orders its children in a grid, left to "
1889  "right, top to bottom, according to the number of columns. "
1890  "The first argument is the number of columns, the second is "
1891  "the "
1892  "spacing between items (both vertically and horizontally), "
1893  "and "
1894  "third is the margins. Both spacing and margins default to "
1895  "zero.")
1896  .def_property_readonly(
1897  "spacing", &VGrid::GetSpacing,
1898  "Returns the spacing between rows and columns")
1899  .def_property_readonly("margins", &VGrid::GetMargins,
1900  "Returns the margins")
1901  .def_property("preferred_width", &VGrid::GetPreferredWidth,
1903  "Sets the preferred width of the layout");
1904 
1905  // ---- Dialog ----
1906  py::class_<Dialog, UnownedPointer<Dialog>, Widget> dialog(m_gui, "Dialog",
1907  "Dialog");
1908  dialog.def(py::init<const char *>(),
1909  "Creates a dialog with the given title");
1910 
1911  // ---- FileDialog ----
1912  py::class_<FileDialog, UnownedPointer<FileDialog>, Dialog> filedlg(
1913  m_gui, "FileDialog", "File picker dialog");
1914 
1915  py::native_enum<FileDialog::Mode> filedlg_mode(
1916  filedlg, "Mode", "enum.Enum", "Enum class for FileDialog modes.");
1917  filedlg_mode.value("OPEN", FileDialog::Mode::OPEN)
1918  .value("SAVE", FileDialog::Mode::SAVE)
1919  .export_values()
1920  .finalize();
1921  filedlg.def(py::init<FileDialog::Mode, const char *, const Theme &>(),
1922  "Creates either an open or save file dialog. The first "
1923  "parameter is either FileDialog.OPEN or FileDialog.SAVE. The "
1924  "second is the title of the dialog, and the third is the "
1925  "theme, "
1926  "which is used internally by the dialog for layout. The theme "
1927  "should normally be retrieved from window.theme.")
1928  .def("set_path", &FileDialog::SetPath,
1929  "Sets the initial path path of the dialog")
1930  .def("add_filter", &FileDialog::AddFilter,
1931  "Adds a selectable file-type filter: "
1932  "add_filter('.stl', 'Stereolithography mesh'")
1933  .def("set_on_cancel", &FileDialog::SetOnCancel,
1934  "Cancel callback; required")
1935  .def("set_on_done", &FileDialog::SetOnDone,
1936  "Done callback; required");
1937 }
1938 
1939 void pybind_gui(py::module &m) {
1940  py::module m_gui = m.def_submodule("gui");
1941  pybind_gui_events(m_gui);
1942  pybind_gui_classes(m_gui);
1943 }
1944 
1945 } // namespace gui
1946 } // namespace visualization
1947 } // namespace cloudViewer
std::shared_ptr< core::Tensor > image
int width
int size
std::string name
int height
math::float4 color
core::Tensor result
Definition: VtkUtils.cpp:76
Bounding box structure.
Definition: ecvBBox.h:25
Contains the pinhole camera intrinsic parameters.
void SetMenubar(std::shared_ptr< Menu > menubar)
std::shared_ptr< geometry::Image > RenderToImage(rendering::Renderer &renderer, rendering::View *view, rendering::Scene *scene, int width, int height)
static constexpr FontId DEFAULT_FONT_ID
Identifier for font used by default for all UI elements.
Definition: Application.h:57
void SetFont(FontId id, const FontDescription &fd)
std::shared_ptr< Menu > GetMenubar() const
void RunInThread(std::function< void()> f)
FontId AddFont(const FontDescription &fd)
void AddWindow(std::shared_ptr< Window > window)
Must be called on the same thread that calls Run()
void Quit()
Closes all the windows, which exits as a result.
void PostToMainThread(Window *window, std::function< void()> f)
std::shared_ptr< geometry::Image > RenderToDepthImage(rendering::Renderer &renderer, rendering::View *view, rendering::Scene *scene, int width, int height, bool z_in_view_space=false)
bool RunOneTick(EnvUnlocker &unlocker, bool cleanup_if_no_windows=true)
void SetOnClicked(std::function< void()> on_clicked)
Definition: Button.cpp:77
void SetText(const char *text)
Sets the text of the button. Do not call if this is an image button.
Definition: Button.cpp:50
float GetHorizontalPaddingEm() const
Returns the padding, in units of ems.
Definition: Button.cpp:52
void SetOnChecked(std::function< void(bool)> on_checked)
Definition: Checkbox.cpp:45
bool GetIsOpen()
Returns true if open and false if collapsed.
Definition: Layout.cpp:388
void SetOnValueChanged(std::function< void(const Color &)> on_value_changed)
Definition: ColorEdit.cpp:44
void SetColor(float r, float g, float b, float a=1.0)
Definition: Color.cpp:39
std::shared_ptr< NumberEdit > GetNumberEdit()
Definition: TreeView.cpp:172
std::shared_ptr< ColorEdit > GetColorEdit()
Definition: TreeView.cpp:176
void RemoveItem(const char *name)
Removes the first item matching the given text.
Definition: Combobox.cpp:74
const char * GetSelectedValue() const
Returns the text of the selected value, or "" if nothing is selected.
Definition: Combobox.cpp:102
bool SetSelectedValue(const char *value)
Definition: Combobox.cpp:117
void ChangeItem(int index, const char *name)
Definition: Combobox.cpp:61
const char * GetItem(int index) const
Definition: Combobox.cpp:96
void SetOnValueChanged(std::function< void(const char *, int)> on_value_changed)
Definition: Combobox.cpp:128
Base class for dialogs.
Definition: Dialog.h:19
void SetOnCancel(std::function< void()> on_cancel)
The OnCancel and OnDone callbacks must be specified.
Definition: FileDialog.cpp:358
void SetOnDone(std::function< void(const char *)> on_done)
The OnCancel and OnDone callbacks must be specified.
Definition: FileDialog.cpp:362
void AddFilter(const char *filter, const char *description)
Definition: FileDialog.cpp:339
constexpr static const char * MONOSPACE
Definition: Font.h:23
void AddTypefaceForCodePoints(const char *typeface, const std::vector< uint32_t > &code_points)
Definition: Font.cpp:31
void AddTypefaceForLanguage(const char *typeface, const char *lang)
Definition: Font.cpp:26
constexpr static const char * SANS_SERIF
Definition: Font.h:22
Lays out widgets horizontally.
Definition: Layout.h:188
void SetUIImage(std::shared_ptr< UIImage > image)
Definition: ImageWidget.cpp:72
std::shared_ptr< UIImage > GetUIImage() const
Definition: ImageWidget.cpp:68
void UpdateImage(std::shared_ptr< geometry::Image > image)
Definition: ImageWidget.cpp:60
std::shared_ptr< Label > GetLabel()
Definition: TreeView.cpp:111
std::shared_ptr< ColorEdit > GetColorEdit()
Definition: TreeView.cpp:113
std::shared_ptr< Checkbox > GetCheckbox()
Definition: TreeView.cpp:107
void SetText(const char *text)
Sets the text of the label (copies text)
Definition: Label3D.cpp:36
void SetPosition(const Eigen::Vector3f &pos)
Definition: Label3D.cpp:40
void SetTextColor(const Color &color)
Definition: Label3D.cpp:46
Eigen::Vector3f GetPosition() const
Definition: Label3D.cpp:38
const char * GetText() const
Definition: Label.cpp:50
void SetText(const char *text)
Sets the text of the label (copies text)
Definition: Label.cpp:52
void SetTextColor(const Color &color)
Definition: Label.cpp:59
void SetFontId(const FontId font_id)
Definition: Label.cpp:63
void AddFixed(int size)
Adds a fixed number of pixels after the previously added widget.
Definition: Layout.cpp:212
void SetOnValueChanged(std::function< void(const char *, bool)> on_value_changed)
Definition: ListView.cpp:71
const char * GetSelectedValue() const
Returns the value of the currently selected item in the list.
Definition: ListView.cpp:49
int GetSelectedIndex() const
Returns the currently selected item in the list.
Definition: ListView.cpp:47
void SetItems(const std::vector< std::string > &items)
Definition: ListView.cpp:42
void SetSelectedIndex(int index)
Selects the indicated row of the list. Does not call onValueChanged.
Definition: ListView.cpp:58
void SetLimits(double min_value, double max_value)
Definition: NumberEdit.cpp:65
void SetOnValueChanged(std::function< void(double)> on_changed)
Definition: NumberEdit.cpp:86
void SetValue(float value)
ProgressBar values ranges from 0.0 (incomplete) to 1.0 (complete)
Definition: ProgressBar.cpp:30
Widget::EventResult Mouse(const MouseEvent &e) override
Definition: gui.cpp:208
PyImageWidget(std::shared_ptr< cloudViewer::geometry::Image > image)
Uses existing image. Each ImageWidget will use one draw call.
Definition: gui.cpp:186
PyImageWidget(cloudViewer::visualization::rendering::TextureHandle texture_id, float u0=0.0f, float v0=0.0f, float u1=1.0f, float v1=1.0f)
Definition: gui.cpp:195
Widget::EventResult Key(const KeyEvent &e) override
Definition: gui.cpp:229
void SetOnKey(std::function< int(const KeyEvent &)> f)
Definition: gui.cpp:206
PyImageWidget(std::shared_ptr< cloudViewer::t::geometry::Image > image)
Uses existing image. Each ImageWidget will use one draw call.
Definition: gui.cpp:189
PyImageWidget(const char *image_path)
Definition: gui.cpp:184
void SetOnMouse(std::function< int(const MouseEvent &)> f)
Definition: gui.cpp:205
void SetOnMouse(std::function< int(const MouseEvent &)> f)
Definition: gui.cpp:259
void SetOnKey(std::function< int(const KeyEvent &)> f)
Definition: gui.cpp:260
Widget::EventResult Mouse(const MouseEvent &e) override
Definition: gui.cpp:262
Widget::EventResult Key(const KeyEvent &e) override
Definition: gui.cpp:283
PyWindow(const std::string &title, int width, int height, int flags=0)
Definition: gui.cpp:84
void Layout(const LayoutContext &context) override
Definition: gui.cpp:97
PyWindow(const std::string &title, int flags=0)
Definition: gui.cpp:82
std::function< void(const LayoutContext &)> on_layout_
Definition: gui.cpp:94
PyWindow(const std::string &title, int x, int y, int width, int height, int flags=0)
Definition: gui.cpp:86
void SetOnSelectionChanged(std::function< void(int)> callback)
Definition: RadioButton.cpp:64
void SetItems(const std::vector< std::string > &items)
Definition: RadioButton.cpp:42
void SetOnSunDirectionChanged(std::function< void(const Eigen::Vector3f &)> on_dir_changed)
std::shared_ptr< Label3D > AddLabel(const Eigen::Vector3f &pos, const char *text)
std::shared_ptr< rendering::CloudViewerScene > GetScene() const
Widget::EventResult Mouse(const MouseEvent &e) override
void SetCenterOfRotation(const Eigen::Vector3f &center)
void SetScene(std::shared_ptr< rendering::CloudViewerScene > scene)
void RemoveLabel(std::shared_ptr< Label3D > label)
void SetupCamera(float verticalFoV, const ccBBox &scene_bounds, const Eigen::Vector3f &center_of_rotation)
void LookAt(const Eigen::Vector3f &center, const Eigen::Vector3f &eye, const Eigen::Vector3f &up)
Widget::EventResult Key(const KeyEvent &e) override
This a vertical layout that scrolls if it is smaller than its contents.
Definition: Layout.h:169
int GetIntValue() const
Returns the value of the control as an integer.
Definition: Slider.cpp:44
void SetLimits(double min_value, double max_value)
Definition: Slider.cpp:60
double GetDoubleValue() const
Returns the value of the control as a double.
Definition: Slider.cpp:46
void SetOnValueChanged(std::function< void(double)> on_value_changed)
Sets a function to call when the value changes because of user action.
Definition: Slider.cpp:70
int GetSelectedIndex() const
Returns the index of the selected child.
void SetSelectedIndex(int index)
Sets the index of the child to draw.
void AddTab(const char *name, std::shared_ptr< Widget > panel)
Definition: TabControl.cpp:44
void SetOnSelectedTabChanged(std::function< void(int)> on_changed)
Definition: TabControl.cpp:50
const char * GetText() const
Returns the current text value displayed.
Definition: TextEdit.cpp:52
void SetPlaceholderText(const char *text)
Sets the text to display if the text value is empty.
Definition: TextEdit.cpp:60
void SetOnValueChanged(std::function< void(const char *)> on_value_changed)
Definition: TextEdit.cpp:69
const char * GetPlaceholderText() const
Returns the text displayed if the text value is empty.
Definition: TextEdit.cpp:56
void SetOnTextChanged(std::function< void(const char *)> on_text_changed)
Definition: TextEdit.cpp:64
void SetOnClicked(std::function< void(bool)> on_clicked)
std::shared_ptr< Widget > GetItem(ItemId item_id) const
Returns item, or nullptr if item_id cannot be found.
Definition: TreeView.cpp:300
void Clear()
Clears all the items.
Definition: TreeView.cpp:294
void SetOnSelectionChanged(std::function< void(ItemId)> on_selection_changed)
Definition: TreeView.cpp:344
void SetSelectedItemId(ItemId item_id)
Selects the indicated item of the list. Does not call onValueChanged.
Definition: TreeView.cpp:340
ItemId AddTextItem(ItemId parent_id, const char *text)
Adds a text item to the tree.
Definition: TreeView.cpp:258
void SetCanSelectItemsWithChildren(bool can_select)
Definition: TreeView.cpp:328
void RemoveItem(ItemId item_id)
Removes an item an all its children (if any) from the tree.
Definition: TreeView.cpp:263
ItemId GetSelectedItemId() const
Returns the currently selected item id in the tree.
Definition: TreeView.cpp:332
ItemId AddItem(ItemId parent_id, std::shared_ptr< Widget > item)
Adds an item to the tree.
Definition: TreeView.cpp:236
@ ASPECT
Scales to any size and aspect ratio.
const Margins & GetMargins() const
Definition: Layout.cpp:573
void SetValue(const Eigen::Vector3f &val)
Sets the value of the widget. Does not call onValueChanged.
Definition: VectorEdit.cpp:38
void SetOnValueChanged(std::function< void(const Eigen::Vector3f &)> on_changed)
Definition: VectorEdit.cpp:46
Lays out widgets vertically.
Definition: Layout.h:112
Widget container to delegate any widget dynamically.
Definition: WidgetProxy.h:47
const Rect & GetFrame() const override
Returns the frame size in pixels.
Definition: WidgetProxy.cpp:47
virtual std::shared_ptr< Widget > GetWidget()
Retrieve current delegated widget.
Definition: WidgetProxy.cpp:31
WidgetStack manages multiple widgets in a stack.
Definition: WidgetStack.h:32
std::shared_ptr< Widget > PopWidget()
Pop the top most widget.
Definition: WidgetStack.cpp:28
void PushWidget(std::shared_ptr< Widget > widget)
Push a widget into stack so the it be the topmost widget.
Definition: WidgetStack.cpp:23
void SetOnTop(std::function< void(std::shared_ptr< Widget >)> onTopCallback)
Setup a callback while a widget is popped out and a new widget becomes the topmost one.
Definition: WidgetStack.cpp:44
virtual const Color & GetBackgroundColor() const
Definition: Widget.cpp:55
virtual void SetTooltip(const char *text)
Definition: Widget.cpp:73
virtual Size CalcPreferredSize(const LayoutContext &context, const Constraints &constraints) const
Definition: Widget.cpp:77
virtual const std::vector< std::shared_ptr< Widget > > GetChildren() const
Definition: Widget.cpp:47
virtual bool IsEnabled() const
Definition: Widget.cpp:69
virtual void SetFrame(const Rect &f)
Definition: Widget.cpp:53
virtual const Rect & GetFrame() const
Returns the frame size in pixels.
Definition: Widget.cpp:51
virtual void SetBackgroundColor(const Color &color)
Definition: Widget.cpp:61
virtual const char * GetTooltip() const
Definition: Widget.cpp:75
virtual void SetVisible(bool vis)
Definition: Widget.cpp:67
virtual EventResult Mouse(const MouseEvent &e)
Definition: Widget.cpp:161
virtual bool IsVisible() const
Definition: Widget.cpp:65
virtual void SetEnabled(bool enabled)
Definition: Widget.cpp:71
virtual EventResult Key(const KeyEvent &e)
Definition: Widget.cpp:185
void SetFocusWidget(Widget *w)
Sets.
Definition: Window.cpp:595
const std::vector< std::shared_ptr< Widget > > & GetChildren() const
Definition: Window.cpp:431
void SetNeedsLayout()
Instructs the window to relayout before the next draw.
Definition: Window.cpp:571
const Theme & GetTheme() const
Definition: Window.cpp:445
Window(const std::string &title, int flags=0)
Definition: Window.cpp:237
void SetSize(const Size &size)
Sets the size of the window in pixels. Includes menubar on Linux.
Definition: Window.cpp:508
void AddChild(std::shared_ptr< Widget > w)
Definition: Window.cpp:597
void SetOnMenuItemActivated(Menu::ItemId item_id, std::function< void()> callback)
Definition: Window.cpp:602
void SetOnKeyEvent(std::function< bool(const KeyEvent &)> callback)
Definition: Window.cpp:615
visualization::rendering::Renderer & GetRenderer() const
Definition: Window.cpp:447
void SetTitle(const char *title)
Definition: Window.cpp:466
virtual void Layout(const LayoutContext &context)
Definition: Window.cpp:681
void ShowDialog(std::shared_ptr< Dialog > dlg)
Definition: Window.cpp:619
void SetOnTickEvent(std::function< bool()> callback)
Definition: Window.cpp:607
void CloseDialog()
Closes the dialog.
Definition: Window.cpp:648
void ShowMessageBox(const char *title, const char *message)
Definition: Window.cpp:661
float GetScaling() const
Returns the scaling factor from OS pixels to device pixels.
Definition: Window.cpp:538
void SetOnClose(std::function< bool()> callback)
Definition: Window.cpp:611
ImGuiContext * context
Definition: Window.cpp:76
const Theme * theme
Definition: Window.cpp:74
static const std::string path
Definition: PointCloud.cpp:59
std::string GetFileParentDirectory(const std::string &filename)
Definition: FileSystem.cpp:314
void pybind_gui_events(py::module &m)
Definition: events.cpp:19
std::shared_ptr< geometry::Image > RenderToImageWithoutWindow(rendering::CloudViewerScene *scene, int width, int height)
Definition: gui.cpp:158
void pybind_gui(py::module &m)
std::shared_ptr< geometry::Image > RenderToDepthImageWithoutWindow(rendering::CloudViewerScene *scene, int width, int height, bool z_in_view_space)
Definition: gui.cpp:165
void pybind_gui_classes(py::module &m_gui)
Definition: gui.cpp:309
void InitializeForPython(std::string resource_path, bool headless)
Definition: gui.cpp:136
std::shared_ptr< T > TakeOwnership(UnownedPointer< T > x)
Definition: visualization.h:49
Generic file read and write utility for python interface.
int GetVert() const
Convenience function that returns top + bottom.
Definition: Layout.cpp:120
int GetHoriz() const
Convenience function that returns left + right.
Definition: Layout.cpp:118
Definition: lsd.c:1170