DearPyGui icon indicating copy to clipboard operation
DearPyGui copied to clipboard

dpg.configure_item() Ignores Translation in Transform Matrix on Draw Nodes

Open jacobnrohan opened this issue 6 months ago • 3 comments

Version of Dear PyGui

Version: 1.11.1 Operating System: Debian Bookworm (also tested on Windows 11)

My Issue

For drawings on a draw node with a transform, dpg.configure_item does not work properly. In these cases, it seems to ignore the 4th column (translate values) of the 4x4 transform matrix.

Deleting and re-creating the drawing produces the expected behavior. When dpg.configure_item() is used, drawings are effected by the scale of the transform matrix, but are not property translated.

To Reproduce

Steps to reproduce the behavior: (see code)

  1. Create a draw node and apply a transformation and drawing
  2. Update that drawing using dpg.configure_item() to change its position
  3. notice how the new position is no longer effected by the transformation's translation, but is still effected by scale

Expected behavior

When dpg.configure_item() is used to change a drawing's position, it should remain fully effected by the draw node transform.

Deleting then re-drawing the item produces the expected behavior but is not a practical alternative.

Screenshots/Video

configure_item

Standalone, minimal, complete and verifiable example

import dearpygui.dearpygui as dpg
from dearpygui._dearpygui import mvMat4

class application:

    def __init__(self, ):

        # init variables for origin/dragging
        self.scale = 0
        self.origin_pre = [200.0, 200.0]
        self.dragfrom = [0.0, 0.0]
        self.dragto = [0.0, 0.0]
        self.__any_hovered__ = False

        # init dpg
        dpg.create_context()
        dpg.create_viewport(title='issue with dpg.configure_item()',vsync=False)
        dpg.setup_dearpygui()

        # init a viewport and draw node
        self.id_background = dpg.add_viewport_drawlist(front=False, tag="background")
        dpg.add_draw_node(tag="draw_node", parent="background")
        dpg.apply_transform("draw_node", self.transform)

        # init left-click-drag to move drawings
        self.dragging_mouse = False
        with dpg.handler_registry():
            dpg.add_mouse_click_handler(callback=self.mouse_click_handler)
            dpg.add_mouse_move_handler(callback=self.mouse_move_handler)
            dpg.add_mouse_release_handler(callback=self.mouse_release_handler)
            dpg.add_mouse_wheel_handler(callback=self.mouse_wheel_handler)

        # add basic drawings
        dpg.draw_circle(center=[0, 0], radius=5, fill=[255, 0, 0], color=[0, 0, 0, 0], parent="draw_node") # origin
        dpg.draw_text(pos=[0, 0], text='(0, 0)', color=[255, 0, 0], size=32, parent="draw_node")
        dpg.draw_circle(center=[200, 0], radius=5, fill=[255, 0, 0], color=[0, 0, 0, 0], parent="draw_node") # origin
        dpg.draw_text(pos=[200, 0], text='(200, 0)', color=[255, 0, 0], size=32, parent="draw_node")
        self.id = dpg.draw_rectangle(pmin=[0, 0], pmax=[100, 100], fill=[0, 0, 255, 128], parent="draw_node")
        dpg.draw_rectangle(pmin=[200, 0], pmax=[300, 100], fill=[0, 0, 0], parent="draw_node")

        # add controls to update drawing
        with dpg.window(label="move object",no_background=False):
            dpg.add_text("left-click and drag the background to update the draw_node transform.")
            dpg.add_text("scroll to zoom in and out.")
            self.button0 = dpg.add_button(label="reset", callback=self.reset)
            self.button1 = dpg.add_button(label="dpg.configure_item", callback=self.move_rectangle)
            self.button2 = dpg.add_button(label="dpg.delete_item and dpg.draw_rectangle", callback=self.replace_rectangle)
        with dpg.tooltip(self.button1, delay=0.10, hide_on_activity=True):
            dpg.add_text('actual behavior: demo shows that dpg.configure_item does not\n'\
                         'properly update drawings on draw nodes w/ transformations.')
        with dpg.tooltip(self.button2, delay=0.10, hide_on_activity=True):
            dpg.add_text('expected behavior')

        # main loop
        dpg.show_viewport()
        dpg.start_dearpygui()
        dpg.destroy_context()

    @property
    def any_hovered(self):
        # https://github.com/hoffstadt/DearPyGui/issues/2281
        return any([s["hovered"] for s in [dpg.get_item_state(w) for w in dpg.get_windows()] if ("hovered" in s.keys())])

    def mouse_click_handler(self, sender, app_data, user_data):
        if app_data == 0: # left click
            if not self.__any_hovered__ and not self.dragging_mouse:
                self.dragging_mouse = True
                xy = dpg.get_mouse_pos(local=False)
                # print('mouse_click_handler xy:', xy)
                self.dragfrom = xy
                self.dragto = xy

    def mouse_move_handler(self, sender, app_data, user_data):
        self.__any_hovered__ = self.any_hovered
        # print("any_hovered?:", self.any_hovered)
        if self.dragging_mouse:
            xy = dpg.get_mouse_pos(local=False)
            # print('mouse_move_handler xy:', xy)
            self.dragto = xy
            dpg.apply_transform("draw_node", self.transform)

    def mouse_release_handler(self, sender, app_data, user_data):
        if app_data == 0: # left click
            if not self.__any_hovered__ and self.dragging_mouse:
                self.dragging_mouse = False
                self.origin_pre = self.origin
                self.dragfrom = [0.0, 0.0]
                self.dragto = [0.0, 0.0]
                # print('mouse_release_handler')

    def mouse_wheel_handler(self, sender, app_data, user_data):
        if not self.__any_hovered__:
            if app_data > 0:
                self.scale += 1
            elif app_data < 0:
                self.scale -= 1
            dpg.apply_transform("draw_node", self.transform)        

    @property
    def origin(self):
        return [o + t - f for o, f, t in zip(self.origin_pre, self.dragfrom, self.dragto)]
    
    @property
    def transform(self):
        s = 2 ** (self.scale / 8)
        x, y = self.origin
        return mvMat4(
              s, 0.0, 0.0, x,
            0.0,  -s, 0.0, y,
            0.0, 0.0, 1.0, 0.0,
            0.0, 0.0, 0.0, 1.0)

    def move_rectangle(self, sender, app_data, user_data):
        dpg.configure_item(self.id, pmin=[200, 0], pmax=[300, 100], parent="draw_node") # TODO: NEEDS FIX

    def replace_rectangle(self, sender, app_data, user_data):
        dpg.delete_item(self.id)
        self.id = dpg.draw_rectangle(pmin=[200, 0], pmax=[300, 100], fill=[0, 0, 255, 128], parent="draw_node")

    def reset(self, sender, app_data, user_data):
        dpg.delete_item(self.id)
        self.id = dpg.draw_rectangle(pmin=[0, 0], pmax=[100, 100], fill=[0, 0, 255, 128], parent="draw_node")

if __name__ == "__main__":
    app = application()

jacobnrohan avatar Aug 22 '24 16:08 jacobnrohan