Skip to content

Commit 4a08c2e

Browse files
committed
cmap picker menu working, going to use cmap next to get textures working
1 parent 8f991f8 commit 4a08c2e

File tree

13 files changed

+313
-72
lines changed

13 files changed

+313
-72
lines changed

fastplotlib/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from .graphics.selectors import *
66
from .graphics.utils import pause_events
77
from .legends import *
8+
from .tools import *
89
from .layouts import Figure
910

1011
from .widgets import ImageWidget

fastplotlib/graphics/_base.py

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@
77
import pylinalg as la
88
from wgpu.gui.base import log_exception
99

10+
try:
11+
from imgui_bundle import imgui
12+
except ImportError:
13+
IMGUI = False
14+
else:
15+
IMGUI = True
16+
1017
import pygfx
1118

1219
from ._features import (
@@ -117,6 +124,8 @@ def __init__(
117124

118125
self._axes: Axes = None
119126

127+
self._right_click_menu = None
128+
120129
@property
121130
def supported_events(self) -> tuple[str]:
122131
"""events supported by this graphic"""
@@ -303,17 +312,6 @@ def _handle_event(self, callback, event: pygfx.Event):
303312
# for feature events
304313
event._target = self.world_object
305314

306-
if isinstance(event, pygfx.PointerEvent):
307-
# map from screen to world space and data space
308-
world_xy = self._plot_area.map_screen_to_world(event)
309-
310-
# subtract offset to map to data
311-
data_xy = world_xy - self.offset
312-
313-
# append attributes
314-
event.x_world, event.y_world = world_xy[:2]
315-
event.x_data, event.y_data = data_xy[:2]
316-
317315
with log_exception(f"Error during handling {event.type} event"):
318316
callback(event)
319317

@@ -437,3 +435,24 @@ def add_axes(self):
437435

438436
self._plot_area.scene.add(self.axes.world_object)
439437
self._axes.update_using_bbox(self.world_object.get_world_bounding_box())
438+
439+
@property
440+
def right_click_menu(self):
441+
return self._right_click_menu
442+
443+
@right_click_menu.setter
444+
def right_click_menu(self, menu):
445+
if not IMGUI:
446+
raise ImportError(
447+
"imgui is required to set right-click menus:\n"
448+
"pip install imgui_bundle"
449+
)
450+
451+
self._right_click_menu = menu
452+
menu.owner = self
453+
454+
def _fpl_request_right_click_menu(self):
455+
pass
456+
457+
def _fpl_close_right_click_menu(self):
458+
pass

fastplotlib/layouts/_imgui_figure.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
import pygfx
1313

1414
from ._figure import Figure
15-
from .ui import BaseGUI, SubplotToolbar, RightClickMenu
15+
from ._utils import make_canvas_and_renderer
16+
from .ui import BaseGUI, SubplotToolbar, RightClickMenu, Popup
17+
from .ui.right_click_menus import ColormapPicker
1618

1719

1820
GUI_EDGES = ["top", "right", "bottom", "left"]
@@ -46,6 +48,9 @@ def __init__(
4648
):
4749
self._guis: dict[str, BaseGUI] = {}
4850

51+
canvas, renderer = make_canvas_and_renderer(canvas, renderer)
52+
self._imgui_renderer = ImguiRenderer(renderer.device, canvas)
53+
4954
super().__init__(
5055
shape=shape,
5156
cameras=cameras,
@@ -58,8 +63,6 @@ def __init__(
5863
names=names,
5964
)
6065

61-
self._imgui_renderer = ImguiRenderer(self.renderer.device, self.canvas)
62-
6366
fronts_path = str(
6467
Path(imgui_bundle.__file__).parent.joinpath(
6568
"assets", "fonts", "Font_Awesome_6_Free-Solid-900.otf"
@@ -82,10 +85,14 @@ def __init__(
8285
)
8386

8487
for subplot in self._subplots.ravel():
85-
toolbar = SubplotToolbar(owner=subplot, fa_icons=self._fa_icons)
88+
toolbar = SubplotToolbar(subplot=subplot, fa_icons=self._fa_icons)
8689
self._subplot_toolbars[subplot.position] = toolbar
8790

88-
self._right_click_menu = RightClickMenu(owner=self, fa_icons=self._fa_icons)
91+
self._right_click_menu = RightClickMenu(figure=self, fa_icons=self._fa_icons)
92+
93+
self._popups: dict[str, Popup] = {}
94+
95+
self.register_popup(ColormapPicker)
8996

9097
@property
9198
def imgui_renderer(self) -> ImguiRenderer:
@@ -106,6 +113,9 @@ def _draw_imgui(self) -> imgui.ImDrawData:
106113
for gui in self._guis.values():
107114
gui.update()
108115

116+
for popup in self._popups.values():
117+
popup.update()
118+
109119
self._right_click_menu.update()
110120

111121
imgui.end_frame()
@@ -157,3 +167,12 @@ def get_pygfx_render_area(self, *args):
157167
ypos = 0
158168

159169
return [xpos, ypos, width, height]
170+
171+
def register_popup(self, popup: Popup.__class__):
172+
self._popups[popup.name] = popup(self)
173+
174+
def open_popup(self, name, pos: tuple[int, int], **kwargs):
175+
if self._popups[name].is_open:
176+
return
177+
178+
self._popups[name].open(pos, **kwargs)

fastplotlib/layouts/_plot_area.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def __init__(
3232
scene: pygfx.Scene,
3333
canvas: WgpuCanvasBase,
3434
renderer: pygfx.WgpuRenderer,
35+
extra_renderers: dict = None,
3536
name: str = None,
3637
):
3738
"""
@@ -120,6 +121,19 @@ def __init__(
120121

121122
self.set_viewport_rect()
122123

124+
def get_figure(self, obj=None):
125+
"""Get Figure instance that contains this plot area"""
126+
if obj is None:
127+
obj = self
128+
129+
if obj.parent.__class__.__name__.endswith("Figure"):
130+
return obj.parent
131+
else:
132+
if obj.parent is None:
133+
raise RecursionError
134+
135+
return self.get_figure(obj=obj.parent)
136+
123137
# several read-only properties
124138
@property
125139
def parent(self):

fastplotlib/layouts/ui/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
from ._base import BaseGUI
1+
from ._base import BaseGUI, Window, EdgeWindow, Popup
22
from ._subplot_toolbar import SubplotToolbar
33
from ._right_click_menu import RightClickMenu

fastplotlib/layouts/ui/_base.py

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,45 @@
1+
import numpy as np
2+
13
from imgui_bundle import imgui
24

35
from .._plot_area import PlotArea
46
from .._figure import Figure
57

68

79
class BaseGUI:
8-
# used for pushing unique ID between multiple figs with identical UI elements
10+
"""
11+
Base class for all ImGUI based GUIs, windows and popups
12+
13+
The main purpose of this base is for setting a unique ID between multiple figs with identical UI elements
14+
15+
This ID can be pushed in subclasses within the `update()` method
16+
"""
917
ID_COUNTER: int = 0
1018

11-
def __init__(
12-
self, owner: PlotArea | Figure, fa_icons: imgui.ImFont, size: int | None
13-
):
19+
def __init__(self):
1420
BaseGUI.ID_COUNTER += 1
1521
self._id_counter = BaseGUI.ID_COUNTER
1622

17-
self._owner = owner
18-
self._fa_icons = fa_icons
23+
def update(self):
24+
"""must be implemented in subclass"""
25+
raise NotImplementedError
1926

27+
28+
class Window(BaseGUI):
29+
pass
30+
31+
32+
class EdgeWindow(Window):
33+
def __init__(self, figure: Figure, size: int, fa_icons: imgui.ImFont, *args, **kwargs):
34+
super().__init__()
35+
36+
self._figure = figure
2037
self._size = size
38+
self._fa_icons = fa_icons
2139

2240
@property
2341
def size(self) -> int | None:
42+
"""width or height of the edge window"""
2443
return self._size
2544

2645
@size.setter
@@ -29,9 +48,28 @@ def size(self, value):
2948
raise TypeError
3049
self._size = value
3150

32-
@property
33-
def owner(self) -> PlotArea | Figure:
34-
return self._owner
3551

36-
def update(self):
37-
pass
52+
class Popup(BaseGUI):
53+
def __init__(self, figure: Figure, fa_icons: imgui.ImFont, *args, **kwargs):
54+
super().__init__()
55+
56+
self._figure = figure
57+
self._fa_icons = fa_icons
58+
59+
self._event_filter_names = set()
60+
61+
def set_event_filter(self, name: str):
62+
x1, y1 = imgui.get_window_pos()
63+
width, height = imgui.get_window_size()
64+
x2, y2 = x1 + width, y1 + height
65+
66+
if name not in self._figure.renderer.event_filters.keys():
67+
self._figure.renderer.event_filters[name] = np.array([[x1 - 1, y1 - 1], [x2 + 4, y2 + 4]])
68+
else:
69+
self._figure.renderer.event_filters[name][:] = [x1 - 1, y1 - 1], [x2 + 4, y2 + 4]
70+
71+
self._event_filter_names.add(name)
72+
73+
def clear_event_filters(self):
74+
for name in self._event_filter_names:
75+
self._figure.renderer.event_filters[name][:] = [-1, -1], [-1, -1]

fastplotlib/layouts/ui/_right_click_menu.py

Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from .._utils import controller_types
66
from .._plot_area import PlotArea
7-
from ._base import BaseGUI
7+
from ._base import Popup
88

99

1010
def flip_axis(subplot: PlotArea, axis: str, flip: bool):
@@ -21,40 +21,27 @@ def flip_axis(subplot: PlotArea, axis: str, flip: bool):
2121
setattr(camera.local, axis_attr, scale * -1)
2222

2323

24-
class RightClickMenu(BaseGUI):
25-
def __init__(self, owner, fa_icons, size=None):
26-
super().__init__(owner=owner, fa_icons=fa_icons, size=None)
27-
self._last_right_click_pos = None
24+
class RightClickMenu(Popup):
25+
def __init__(self, figure, fa_icons):
26+
super().__init__(figure=figure, fa_icons=fa_icons)
2827

28+
self._last_right_click_pos = None
2929
self._mouse_down: bool = False
3030

31-
self.owner.renderer.event_filters["right-click-menu"] = np.array(
32-
[[-1, -1], [-1, -1]]
33-
)
34-
35-
self.owner.renderer.event_filters["controller-menu"] = np.array(
36-
[[-1, -1], [-1, -1]]
37-
)
38-
39-
def reset_event_filters(self):
40-
for k in ["right-click-menu", "controller-menu"]:
41-
self.owner.renderer.event_filters[k][:] = [-1, -1], [-1, -1]
42-
43-
def set_event_filter(self, name: str):
44-
x1, y1 = imgui.get_window_pos()
45-
width, height = imgui.get_window_size()
46-
x2, y2 = x1 + width, y1 + height
47-
48-
self.owner.renderer.event_filters[name][:] = [x1 - 1, y1 - 1], [x2 + 4, y2 + 4]
31+
self.is_open: bool = False
4932

5033
def get_subplot(self) -> PlotArea | bool:
5134
if self._last_right_click_pos is None:
5235
return False
5336

54-
for subplot in self.owner:
37+
for subplot in self._figure:
5538
if subplot.viewport.is_inside(*self._last_right_click_pos):
5639
return subplot
5740

41+
def cleanup(self):
42+
self.clear_event_filters()
43+
self.is_open = False
44+
5845
def update(self):
5946
if imgui.is_mouse_down(1) and not self._mouse_down:
6047
self._mouse_down = True
@@ -70,7 +57,7 @@ def update(self):
7057
imgui.open_popup(f"right-click-menu")
7158

7259
if not imgui.is_popup_open("right-click-menu"):
73-
self.reset_event_filters()
60+
self.cleanup()
7461

7562
if imgui.begin_popup(f"right-click-menu"):
7663
self.set_event_filter("right-click-menu")
@@ -81,6 +68,7 @@ def update(self):
8168
# subplot is returned
8269
imgui.end_popup()
8370
imgui.close_current_popup()
71+
self.cleanup()
8472
return
8573

8674
name = self.get_subplot().name

0 commit comments

Comments
 (0)