Skip to content

Commit 9a8a1b8

Browse files
authored
Adding top gui to fpl (#999)
* top gui * fix * proper logic * allow for y and x offset scaling * also fix for setting by pixel * add screenshot * small fixes
1 parent 0874498 commit 9a8a1b8

5 files changed

Lines changed: 102 additions & 13 deletions

File tree

examples/guis/imgui_top.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""
2+
ImGUI Header GUI
3+
================
4+
5+
Basic examples demonstrating how to use create a header gui
6+
"""
7+
8+
# test_example = true
9+
# sphinx_gallery_pygfx_docs = 'screenshot'
10+
11+
import numpy as np
12+
import fastplotlib as fpl
13+
14+
# subclass from EdgeWindow to make a custom ImGUI Window to place inside the figure!
15+
from fastplotlib.ui import EdgeWindow
16+
from imgui_bundle import imgui
17+
18+
# make some initial data
19+
np.random.seed(0)
20+
21+
xs = np.linspace(0, np.pi * 10, 100)
22+
ys = np.sin(xs) + np.random.normal(scale=0.0, size=100)
23+
data = np.column_stack([xs, ys])
24+
25+
26+
# make a figure
27+
figure = fpl.Figure(size=(700, 560))
28+
29+
# make some scatter points at every 10th point
30+
figure[0, 0].add_scatter(data[::10], colors="cyan", sizes=15, name="sine-scatter", uniform_color=True)
31+
32+
# place a line above the scatter
33+
figure[0, 0].add_line(data, thickness=3, colors="r", name="sine-wave", uniform_color=True)
34+
35+
36+
class ImguiExample(EdgeWindow):
37+
def __init__(self, figure, size, location, title):
38+
super().__init__(figure=figure, size=size, location=location, title=title, window_flags=imgui.WindowFlags_.no_title_bar | imgui.WindowFlags_.no_resize)
39+
40+
def update(self):
41+
imgui.text("This is a top window")
42+
43+
44+
# make GUI instance
45+
gui = ImguiExample(
46+
figure, # the figure this GUI instance should live inside
47+
size=30, # width or height of the GUI window within the figure
48+
location="top", # the edge to place this window at
49+
title=" ", # window title
50+
)
51+
52+
# add it to the figure
53+
figure.add_gui(gui)
54+
55+
figure.show()
56+
57+
# NOTE: fpl.loop.run() should not be used for interactive sessions
58+
# See the "JupyterLab and IPython" section in the user guide
59+
if __name__ == "__main__":
60+
print(__doc__)
61+
fpl.loop.run()

examples/screenshots/imgui_top.png

18 KB
Loading

fastplotlib/layouts/_imgui_figure.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ def add_gui(self, gui: EdgeWindow):
195195

196196
self._fpl_reset_layout()
197197

198+
198199
def get_pygfx_render_area(self, *args) -> tuple[int, int, int, int]:
199200
"""
200201
Get rect for the portion of the canvas that the pygfx renderer draws to,
@@ -208,6 +209,8 @@ def get_pygfx_render_area(self, *args) -> tuple[int, int, int, int]:
208209
"""
209210

210211
width, height = self.canvas.get_logical_size()
212+
x = 0
213+
y = 0
211214

212215
for edge in ["right"]:
213216
if self.guis[edge]:
@@ -217,7 +220,12 @@ def get_pygfx_render_area(self, *args) -> tuple[int, int, int, int]:
217220
if self.guis[edge]:
218221
height -= self._guis[edge].size
219222

220-
return 0, 0, max(1, width), max(1, height)
223+
for edge in ["top"]:
224+
if self.guis[edge]:
225+
y += self._guis[edge].size
226+
height -= self._guis[edge].size
227+
228+
return x, y, max(1, width), max(1, height)
221229

222230
def register_popup(self, popup: Popup.__class__):
223231
"""

fastplotlib/layouts/_rect.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ def _set(self, rect):
3939

4040
def _set_from_fract(self, rect):
4141
"""set rect from fractional representation"""
42-
_, _, cw, ch = self._canvas_rect
43-
mult = np.array([cw, ch, cw, ch])
42+
rect = np.asarray(rect, dtype=float).copy()
43+
x_offset, y_offset, cw, ch = self._canvas_rect
4444

4545
# check that widths, heights are valid:
4646
if rect[0] + rect[2] > 1:
@@ -54,25 +54,35 @@ def _set_from_fract(self, rect):
5454

5555
# assign values to the arrays, don't just change the reference
5656
self._rect_frac[:] = rect
57-
self._rect_screen_space[:] = self._rect_frac * mult
57+
x_px = x_offset + rect[0] * cw
58+
y_px = y_offset + rect[1] * ch
59+
w_px = rect[2] * cw
60+
h_px = rect[3] * ch
61+
self._rect_screen_space[:] = np.array([x_px, y_px, w_px, h_px])
5862

5963
def _set_from_screen_space(self, rect):
6064
"""set rect from screen space representation"""
61-
_, _, cw, ch = self._canvas_rect
65+
x_offset, y_offset, cw, ch = self._canvas_rect
6266
mult = np.array([cw, ch, cw, ch])
6367
# for screen coords allow (x, y) = 1 or 0, but w, h must be > 1
6468
# check that widths, heights are valid
65-
if rect[0] + rect[2] > cw:
69+
70+
# account for potential x and y offset
71+
rect_offset = rect.copy()
72+
rect_offset[0] -= x_offset
73+
rect_offset[1] -= y_offset
74+
75+
if rect_offset[0] + rect_offset[2] > cw:
6676
raise ValueError(
67-
f"invalid rect: {rect}\n x + width > canvas width: {rect[0]} + {rect[2]} > {cw}"
77+
f"invalid rect: {rect}\n x + width > canvas width: {rect_offset[0]} + {rect_offset[2]} > {cw}"
6878
)
69-
if rect[1] + rect[3] > ch:
79+
if rect_offset[1] + rect_offset[3] > ch:
7080
raise ValueError(
71-
f"invalid rect: {rect}\n y + height > canvas height: {rect[1]} + {rect[3]} >{ch}"
81+
f"invalid rect: {rect}\n y + height > canvas height: {rect_offset[1]} + {rect_offset[3]} >{ch}"
7282
)
7383

74-
self._rect_frac[:] = rect / mult
75-
self._rect_screen_space[:] = rect
84+
self._rect_frac[:] = rect_offset / mult
85+
self._rect_screen_space[:] = rect_offset
7686

7787
@property
7888
def x(self) -> np.float64:

fastplotlib/ui/_base.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from ..layouts._figure import Figure
88

99

10-
GUI_EDGES = ["right", "bottom"]
10+
GUI_EDGES = ["right", "bottom", "top"]
1111

1212

1313
class BaseGUI:
@@ -41,7 +41,7 @@ def __init__(
4141
self,
4242
figure: Figure,
4343
size: int,
44-
location: Literal["bottom", "right"],
44+
location: Literal["bottom", "right", "top"],
4545
title: str,
4646
window_flags: enum.IntFlag = imgui.WindowFlags_.no_collapse
4747
| imgui.WindowFlags_.no_resize,
@@ -180,6 +180,16 @@ def get_rect(self) -> tuple[int, int, int, int]:
180180
if self._figure.guis["bottom"] is not None:
181181
height -= self._figure.guis["bottom"].size
182182

183+
if self._figure.guis["top"] is not None:
184+
# decrease the height
185+
height -= self._figure.guis["top"].size
186+
# increase the y start
187+
y_pos += self._figure.guis["top"].size
188+
189+
case "top":
190+
x_pos, y_pos = (0, 0)
191+
width, height = (width_canvas, self.size)
192+
183193
return x_pos, y_pos, width, height
184194

185195
def draw_window(self):

0 commit comments

Comments
 (0)