Skip to content

imgui prototype #552

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 9 commits into from
Closed

imgui prototype #552

wants to merge 9 commits into from

Conversation

kushalkolar
Copy link
Member

@kushalkolar kushalkolar commented Jul 11, 2024

Very early dirty prototype, works in qt windows and gridplots, glfw is crashing for some reason.

We also need to figure out the big-picture of how we implement it, and sequester it. There will always be people who just want to use Qt or some other framework for making their UI. If someone doesn't want to make their UI they can use the built-in imgui, otherwise they don't need to install imgui and can use their preferred framework :D

Can probably have a UIBaseClass for all imgui UIs that fastplotlib uses, and Figure will take an instance of UIClass as an instance and call the UIClass.update() method. Figure will give it the font and any other config, such as the canvas, maybe makes a unique ID_COUNTER for each UIClass instance? See if having multiple ID_COUNTER that all have diff start addresses works so that user-implemented UIs don't have to worry about pushing ids.

Relies on pygfx/wgpu-py#539

image

ImageWidget:

imgui-2024-07-11_21.50.32.mp4

@kushalkolar
Copy link
Member Author

kushalkolar commented Jul 12, 2024

Discussion with @clewis7

  • hlut colorbar right click, choose cmap from big dropdown with pictures and names of cmaps
  • right click menu in subplots
    • All the toolbar options
    • clear subplot -> with confirm popup
    • clear figure -> with confirm popup
    • Graphics list submenu, clicking it opens a small window with info and ability to change stuff, this window has:
      • Buttons to add diff selectors -> when clicked, it asks for a name for the selector
      • button to delete_graphic -> with confirm popup
      • lineedit to change name
      • other properties, rotation, offset
      • graphic specific options
    • controller options, shows a small window kinda like the pyqtgraph one
      • include/exclude filters for each axis
      • damping
  • Record button toolbar and option in right-click menu
    • pops up a window with options for recording
      • path, codec, quality, etc....
  • screenshot, both toolbar and right click menu
  • Axes
    • tick stuff
      • spacing, font size, etc.
    • visible for each dimension
    • grid
      • visible for each plane
      • major and minor step

Idea for managing/sequestering: subclass Figure into a new class FigureWithImgui with the following structure:

class ImguiFigure
def __init__(self, *args, **kwargs):
    super()
    self.imgui_renderer ....
    # more imgui init stuff
    self._guis = list()

def render(self):
    super()
    # imgui render stuff
    for gui in self.guis:
        gui.update()
    imgui.end_frame()
    ...
    canvas.request_draw()

def add_ui(self, edge: str, callable_or_instance_TBD):  # edge is the top, bottom, left, or right edge of the canvas
    # parse edge and set Figure bbox, i.e. [x, y, width, height] that the pygfx renderer uses
    self._guis["edge"] = callable_or_instance_TBD

def set_ui_size(self, edge: str, size: int):
    # set imgui reserved size
    self._edges["edge"].size = size

@property
def width(self) -> int
    return self.renderer.logical_size[0] - self._edges["left"] -self._edges["right"]

# likewise properties for height, x, and y
# use these [x, y, width, height] properties elsewhere in the `Figure`
# such as the subplots and other `PlotAreas` instead of `renderer.logical_size`

Determine whether to import FigureBasic or FigureWithImgui similar to how the jupyter, qt, glfw import is done.

@kushalkolar
Copy link
Member Author

Something we will have to solve with right click menus, if an item in the right click menu is clicked we have to stop that click event from propagating to the pygfx stuff below the menu. See if imgui can do pointer capture.

@kushalkolar
Copy link
Member Author

Idea: use imgui for the legends since it already has layouting etc for this type of use case. But then we're forcing people to use imgui. Will think more about this.

@kushalkolar
Copy link
Member Author

Maybe instead of letting imgui detect and handle the right click event for creating the right click menu popup: add an event handler to the renderer and any graphics for which we want specific right click events. Have them add to a "request queue", for example right click on hlut tool colorbar will request a specific popup menu to be shown on the next render cycle. In each render cycle we go through the queue. Will need to see if we can execute all items in the queue, or if we just do the first one and discard the rest? This might get messy...

@kushalkolar
Copy link
Member Author

Right click menu for hlut tool to reset vmin vmax

@kushalkolar
Copy link
Member Author

We can use the imgui windows to set the viewport rect of subplots, just an idea for the future, needs more protyping & thought to implement. Not committed to this. Might be better to handle moving and resizing subplots directly via pygfx, perhaps after the viewports & renderers refactor.

playing_imgui-2024-07-19_00.51.20.mp4

Messy implemention:

  1. remove the width and height spacings from _subplot
  2. add this to Figure.__init__: io.config_windows_move_from_title_bar_only = True

subplot toolbar:

from imgui_bundle import imgui, icons_fontawesome_6 as fa, imgui_ctx

from .._plot_area import PlotArea


ID_COUNTER = 0


class SubplotToolbar:
    def __init__(self, subplot: PlotArea, icons: imgui.ImFont):
        self._subplot = subplot
        self.icons = icons

        # required to prevent conflict with multiple Figures
        global ID_COUNTER
        ID_COUNTER += 1

        self.id = ID_COUNTER

        self.bbox = subplot.get_rect()
        self._subplot.get_rect = self.get_rect

    def get_rect(self):
        x, y, width, height = self.bbox

        return x + 10, y + 50, width - 10, height - 60

    def update(self):
        x, y, width, height = self._subplot.get_rect()

        # pos = (x, y + height)

        size = self.bbox[2:]
        pos = self.bbox[:2]

        imgui.set_next_window_size(size, imgui.Cond_.appearing)
        imgui.set_next_window_pos(pos, imgui.Cond_.appearing)
        flags = imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.no_background #| imgui.WindowFlags_.no_title_bar

        imgui.begin(f"Toolbar-{self._subplot.position}", p_open=None, flags=flags)

        width, height = imgui.get_window_size()
        x, y = imgui.get_window_pos()


        imgui.push_font(self.icons)

        imgui.push_id(self.id)  # push ID to prevent conflict between multiple figs with same UI
        with imgui_ctx.begin_horizontal(f"toolbar-{self._subplot.position}"):
            # autoscale button
            if imgui.button(fa.ICON_FA_MAXIMIZE):
                self._subplot.auto_scale()
            imgui.pop_font()
            if imgui.is_item_hovered(0):
                imgui.set_tooltip("autoscale scene")

            # center scene
            imgui.push_font(self.icons)
            if imgui.button(fa.ICON_FA_ALIGN_CENTER):
                self._subplot.center_scene()

            # checkbox controller
            _, self._subplot.controller.enabled = imgui.checkbox(fa.ICON_FA_COMPUTER_MOUSE, self._subplot.controller.enabled)

            # checkbox maintain_apsect
            _, self._subplot.camera.maintain_aspect = imgui.checkbox(fa.ICON_FA_EXPAND, self._subplot.camera.maintain_aspect)

            imgui.pop_font()

            _, flip_y = imgui.checkbox("flip-y", self._subplot.camera.local.scale_y < 0)
            if flip_y and self._subplot.camera.local.scale_y > 0:
                self._subplot.camera.local.scale_y *= -1
            elif not flip_y and self._subplot.camera.local.scale_y < 0:
                self._subplot.camera.local.scale_y *= -1

        imgui.pop_id()

        self.bbox = (x, y, width, height)
        self._subplot.set_viewport_rect()

        imgui.end()

@kushalkolar kushalkolar reopened this Jul 19, 2024
@kushalkolar
Copy link
Member Author

@clewis7 let's forget about using imgui to control subplot viewports etc. for this year, we can just focus on the ideas outlined above in #552 (comment) , i.e. toolbar, imagewidget stuff, sequester imgui, and have a way for users to add their own UI on one of the canvas edges. I think that's enough for now and plenty.

Controlling subplot viewports like this and other things open up a can of worms, scope explosion, and change how the whole fpl API works. We can think about it next year?

@kushalkolar
Copy link
Member Author

We can use the imgui windows to set the viewport rect of subplots, just an idea for the future, needs more protyping & thought to implement. Not committed to this. Might be better to handle moving and resizing subplots directly via pygfx, perhaps after the viewports & renderers refactor.

I have an idea to build upon this, user could right click -> drag to draw a rectangle, and then this rectangle becomes a new subplot on an empty figure canvas 😆

@kushalkolar
Copy link
Member Author

superseeded by #571

@kushalkolar kushalkolar deleted the imgui branch October 7, 2024 06:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy