diff --git a/examples/drag_drop_file_input.py b/examples/drag_drop_file_input.py new file mode 100644 index 00000000..1fc6b2d6 --- /dev/null +++ b/examples/drag_drop_file_input.py @@ -0,0 +1,104 @@ +""" +6 cubes with drag and drop texture loading. + +For each box, locate an image in your File Manager, and drag and drop onto the box. + +Currently only working with the Pyglet backend. +""" +from pathlib import Path +import os + +import moderngl +import moderngl_window + +from pyrr import Matrix44 + + +class Cubes(moderngl_window.WindowConfig): + title = "Cubes" + resizable = True + aspect_ratio = None + resource_dir = Path(__file__).parent.resolve() / 'resources' + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + # Load the 6 different boxes with different vertex formats + self.box_top_left = self.load_scene('scenes/box/box-T2F_V3F.obj') + self.box_top_middle = self.load_scene('scenes/box/box-T2F_V3F.obj') + self.box_top_right = self.load_scene('scenes/box/box-T2F_V3F.obj') + self.box_bottom_left = self.load_scene('scenes/box/box-T2F_V3F.obj') + self.box_bottom_middle = self.load_scene('scenes/box/box-T2F_V3F.obj') + self.box_bottom_right = self.load_scene('scenes/box/box-T2F_V3F.obj') + + self.resize(*self.wnd.size) + + def render(self, time, frame_time): + self.ctx.enable_only(moderngl.DEPTH_TEST | moderngl.CULL_FACE) + rot = Matrix44.from_eulers((time, time/2, time/3)) + + # Box top left + view = Matrix44.from_translation((-5, 2, -10), dtype='f4') + self.box_top_left.draw(self.projection, view * rot) + + # Box top middle + view = Matrix44.from_translation((0, 2, -10), dtype='f4') + self.box_top_middle.draw(self.projection, view * rot) + + # Box top right + view = Matrix44.from_translation((5, 2, -10), dtype='f4') + self.box_top_right.draw(self.projection, view * rot) + + # Box bottom left + view = Matrix44.from_translation((-5, -2, -10), dtype='f4') + self.box_bottom_left.draw(self.projection, view * rot) + + # Box bottom middle + view = Matrix44.from_translation((0, -2, -10), dtype='f4') + self.box_bottom_middle.draw(self.projection, view * rot) + + # Box bottom right + view = Matrix44.from_translation((5, -2, -10), dtype='f4') + self.box_bottom_right.draw(self.projection, view * rot) + + def resize(self, width, height): + self.ctx.viewport = 0, 0, width, height + self.projection = Matrix44.perspective_projection(45, width / height, 1, 50, dtype='f4') + + def _load_texture(self, path): + tex = self.load_texture_2d(os.path.relpath(path, self.resource_dir)) + print(type(tex)) + return tex + + def files_dropped_event(self, x, y, paths): + if x < self.wnd._window.width * 0.33: + if y < self.wnd._window.height * 0.5: + # Modify top left box + self.box_top_left.materials[0].mat_texture.texture = \ + self._load_texture(paths[0]) + else: + # Modify bottom left box + self.box_bottom_left.materials[0].mat_texture.texture = \ + self._load_texture(paths[0]) + elif x < self.wnd._window.width * 0.66: + if y < self.wnd._window.height * 0.5: + # Modify top middle box + self.box_top_middle.materials[0].mat_texture.texture = \ + self._load_texture(paths[0]) + else: + # Modify bottom middle box + self.box_bottom_middle.materials[0].mat_texture.texture = \ + self._load_texture(paths[0]) + else: + if y < self.wnd._window.height * 0.5: + # Modify top right box + self.box_top_right.materials[0].mat_texture.texture = \ + self._load_texture(paths[0]) + else: + # Modify bottom right box + self.box_bottom_right.materials[0].mat_texture.texture = \ + self._load_texture(paths[0]) + print(paths) + +if __name__ == '__main__': + Cubes.run() diff --git a/moderngl_window/context/base/window.py b/moderngl_window/context/base/window.py index be81a5d8..ac2898dd 100644 --- a/moderngl_window/context/base/window.py +++ b/moderngl_window/context/base/window.py @@ -4,7 +4,7 @@ import logging import sys import weakref -from typing import Any, Tuple, Type +from typing import Any, Tuple, Type, List import moderngl from moderngl_window.context.base import KeyModifiers, BaseKeys @@ -128,6 +128,7 @@ def __init__( self._mouse_drag_event_func = dummy_func self._mouse_scroll_event_func = dummy_func self._unicode_char_entered_func = dummy_func + self._files_dropped_event_func = dummy_func # Internal states self._ctx = None # type: moderngl.Context @@ -445,6 +446,7 @@ def config(self, config): self.unicode_char_entered_func = getattr( config, "unicode_char_entered", dummy_func ) + self.files_dropped_event_func = getattr(config, "files_dropped_event", dummy_func) self._config = weakref.ref(config) @@ -476,11 +478,21 @@ def close_func(self): """callable: Get or set the close callable""" return self._close_func + @property + def files_dropped_event_func(self): + """callable: Get or set the files_dropped callable""" + return self._files_dropped_event_func + @close_func.setter @require_callable def close_func(self, func): self._close_func = func + @files_dropped_event_func.setter + @require_callable + def files_dropped_event_func(self, func): + self._files_dropped_event_func = func + @property def iconify_func(self): """callable: Get or set ehe iconify/show/hide callable""" @@ -596,6 +608,27 @@ def _handle_mouse_button_state_change(self, button: int, pressed: bool): else: raise ValueError("Incompatible mouse button number: {}".format(button)) + def _convert_window_coordinates(self, x, y, x_flipped=False, y_flipped=False): + """ + Convert window coordinates to top-left coordinate space. + The default origin is the top left corner of the window. + + Args : + x_flipped (bool) - if the input x origin is flipped + y_flipped (bool) - if the input y origin is flipped + Returns: + tuple (x, y) of converted window coordinates + + If you are converting from bottom origin coordinates use x_flipped=True + If you are converting from right origin coordinates use y_flipped=True + """ + if not y_flipped and not x_flipped: + return (x, y) + elif y_flipped and not x_flipped: + return (x, self.height - y) + else: + return(self.width - x, self.height - y) + def is_key_pressed(self, key) -> bool: """Returns: The press state of a key""" return self._key_pressed_map.get(key) is True @@ -1007,6 +1040,16 @@ def resize(self, width: int, height: int): def close(self): """Called when the window is closed""" + def files_dropped(self, x:int , y:int, paths:List[str]): + """ + Called when files dropped onto the window + + Args: + x (int): X location in window where file was dropped + y (int): Y location in window where file was dropped + paths (list): List of file paths dropped + """ + def iconify(self, iconified: bool): """ Called when the window is minimized/iconified diff --git a/moderngl_window/context/pyglet/window.py b/moderngl_window/context/pyglet/window.py index 66e85889..28718860 100644 --- a/moderngl_window/context/pyglet/window.py +++ b/moderngl_window/context/pyglet/window.py @@ -56,6 +56,7 @@ def __init__(self, **kwargs): vsync=self._vsync, fullscreen=self._fullscreen, config=config, + file_drops=True ) self.cursor = self._cursor @@ -72,6 +73,7 @@ def __init__(self, **kwargs): self._window.event(self.on_text) self._window.event(self.on_show) self._window.event(self.on_hide) + self._window.event(self.on_file_drop) self.init_mgl_context() self._buffer_width, self._buffer_height = self._window.get_framebuffer_size() @@ -322,6 +324,20 @@ def on_hide(self): """Called when window is minimized""" self._iconify_func(True) + def on_file_drop(self, x, y, paths): + """Called when files dropped onto the window + + Args: + x (int): X location in window where file was dropped + y (int): Y location in window where file was dropped + paths (list): List of file paths dropped + """ + # pyglet coordinate origin is in the bottom left corner of the window + # mglw coordinate origin is in the top left corner of the window + # convert pyglet coordinates to mglw coordinates: + (x, y) = self._convert_window_coordinates(x, y, y_flipped=True) + self._files_dropped_event_func(x, y, paths) + def destroy(self): """Destroy the pyglet window""" pass