From c68dc01adb3dc2c1d720d06c734fd6036b5e2cdb Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Wed, 7 May 2025 21:48:54 -0400 Subject: [PATCH] allow floating windows, add basic debug window --- fastplotlib/layouts/_imgui_figure.py | 34 +++++++++++++++------------ fastplotlib/ui/__init__.py | 1 + fastplotlib/ui/_base.py | 7 +++++- fastplotlib/ui/_debug.py | 35 ++++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 16 deletions(-) create mode 100644 fastplotlib/ui/_debug.py diff --git a/fastplotlib/layouts/_imgui_figure.py b/fastplotlib/layouts/_imgui_figure.py index 40145fe50..094203c2e 100644 --- a/fastplotlib/layouts/_imgui_figure.py +++ b/fastplotlib/layouts/_imgui_figure.py @@ -12,7 +12,7 @@ import pygfx from ._figure import Figure -from ..ui import EdgeWindow, SubplotToolbar, StandardRightClickMenu, Popup, GUI_EDGES +from ..ui import Window, EdgeWindow, SubplotToolbar, StandardRightClickMenu, Popup, GUI_EDGES from ..ui import ColormapPicker @@ -45,7 +45,7 @@ def __init__( size: tuple[int, int] = (500, 300), names: list | np.ndarray = None, ): - self._guis: dict[str, EdgeWindow] = {k: None for k in GUI_EDGES} + self._guis: dict[str, Window] = {k: None for k in GUI_EDGES} super().__init__( shape=shape, @@ -101,7 +101,7 @@ def __init__( self.register_popup(ColormapPicker) @property - def guis(self) -> dict[str, EdgeWindow]: + def guis(self) -> dict[str, Window]: """GUI windows added to the Figure""" return self._guis @@ -148,30 +148,34 @@ def _draw_imgui(self) -> imgui.ImDrawData: return imgui.get_draw_data() - def add_gui(self, gui: EdgeWindow): + def add_gui(self, gui: Window): """ Add a GUI to the Figure. GUIs can be added to the top, bottom, left or right edge. Parameters ---------- - gui: EdgeWindow - A GUI EdgeWindow instance + gui: Window + A GUI Window instance """ - if not isinstance(gui, EdgeWindow): + if not isinstance(gui, Window): raise TypeError( - f"GUI must be of type: {EdgeWindow} you have passed a {type(gui)}" + f"GUI must be of type: {Window} you have passed a {type(gui)}" ) - location = gui.location + if isinstance(gui, EdgeWindow): + location = gui.location - if location not in GUI_EDGES: - raise ValueError( - f"GUI does not have a valid location, valid locations are: {GUI_EDGES}, you have passed: {location}" - ) + if location not in GUI_EDGES: + raise ValueError( + f"GUI does not have a valid location, valid locations are: {GUI_EDGES}, you have passed: {location}" + ) - if self.guis[location] is not None: - raise ValueError(f"GUI already exists in the desired location: {location}") + if self.guis[location] is not None: + raise ValueError(f"GUI already exists in the desired location: {location}") + else: + # just use mem address for a location key + location = hex(id(gui)) self.guis[location] = gui diff --git a/fastplotlib/ui/__init__.py b/fastplotlib/ui/__init__.py index a1e57a9c5..b4efa2708 100644 --- a/fastplotlib/ui/__init__.py +++ b/fastplotlib/ui/__init__.py @@ -1,3 +1,4 @@ from ._base import BaseGUI, Window, EdgeWindow, Popup, GUI_EDGES from ._subplot_toolbar import SubplotToolbar from .right_click_menus import StandardRightClickMenu, ColormapPicker +from ._debug import DebugWindow diff --git a/fastplotlib/ui/_base.py b/fastplotlib/ui/_base.py index 6c134d415..94dc7f151 100644 --- a/fastplotlib/ui/_base.py +++ b/fastplotlib/ui/_base.py @@ -31,8 +31,13 @@ def update(self): class Window(BaseGUI): """Base class for imgui windows drawn within Figures""" + def draw_window(self): + """ + Must be implemented in subclass - pass + This must include the imgui.begin() and imgui.end() calls that define an imgui window. + """ + raise NotImplementedError class EdgeWindow(Window): diff --git a/fastplotlib/ui/_debug.py b/fastplotlib/ui/_debug.py new file mode 100644 index 000000000..cbbe0c3d4 --- /dev/null +++ b/fastplotlib/ui/_debug.py @@ -0,0 +1,35 @@ +from imgui_bundle import imgui +from icecream import ic + +from ._base import Window + + +class DebugWindow(Window): + def __init__(self, objs: list): + self._objs = objs + super().__init__() + + @property + def objs(self) -> tuple: + return tuple(self._objs) + + def add(self, obj): + self._objs.append(obj) + + def draw_window(self): + imgui.set_next_window_pos((300, 0), imgui.Cond_.appearing) + imgui.set_next_window_pos((0, 0), imgui.Cond_.appearing) + + imgui.begin("Debug", None) + + info = list() + + for obj in self.objs: + if callable(obj): + info.append(ic.format(obj())) + else: + info.append(ic.format(obj)) + + imgui.text_wrapped("\n\n".join(info)) + + imgui.end()