-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1dfe317
commit 0cf7ace
Showing
13 changed files
with
716 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
.idea/ | ||
|
||
# Byte-compiled / optimized / DLL files | ||
__pycache__/ | ||
*.py[cod] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import threading | ||
|
||
from . import main as __main | ||
from . import setup as __setup | ||
from .main import DROPEFFECT, KEYSTATE | ||
from .main import DragAndDrop, DragAndDropDataObject | ||
from .main import set_drag_enter, set_drag_over, set_drag_leave, set_drop | ||
from .main import set_drop_effect, get_drop_effect | ||
|
||
__version__ = "1.0.0" | ||
|
||
def initialize(): | ||
if isinstance(__main._DragAndDropForFunctions, type): | ||
__main._DragAndDropForFunctions = __main._DragAndDropForFunctions() | ||
|
||
threading.Thread(target=__setup.setup, daemon=True).start() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
from __future__ import annotations | ||
|
||
import traceback | ||
from enum import IntEnum | ||
from pathlib import Path | ||
from typing import Type, TypeVar, Dict, Callable, Any, Union, List | ||
|
||
import dearpygui.dearpygui as dpg | ||
|
||
DragAndDropDataObject = Union[None, str, List[Path]] | ||
SubscriptionTag = TypeVar('SubscriptionTag', bound=int) | ||
|
||
|
||
class KEYSTATE(IntEnum): | ||
LEFT = 1 | ||
RIGHT = 2 | ||
SHIFT = 4 | ||
CTRL = 8 | ||
MIDDLE = 16 | ||
ALT = 32 | ||
|
||
|
||
class DROPEFFECT(IntEnum): | ||
NONE = 0 | ||
COPY = 1 | ||
MOVE = 2 | ||
|
||
|
||
_now_drop_effect: DROPEFFECT = DROPEFFECT.MOVE | ||
|
||
|
||
def set_drop_effect(effect: DROPEFFECT = DROPEFFECT.NONE): | ||
global _now_drop_effect | ||
_now_drop_effect = effect | ||
|
||
|
||
def get_drop_effect() -> DROPEFFECT: | ||
return _now_drop_effect | ||
|
||
|
||
class DragAndDrop(): | ||
__subscribers: Dict[SubscriptionTag, Type[DragAndDrop]] = {} | ||
__subscription_tag: SubscriptionTag = None | ||
|
||
def DragEnter(self, dataObject: DragAndDropDataObject, keyState: list[KEYSTATE]): | ||
... | ||
|
||
def DragOver(self, keyState: list[KEYSTATE]): | ||
... | ||
|
||
def DragLeave(self): | ||
... | ||
|
||
def Drop(self, dataObject: DragAndDropDataObject, keyState: list[KEYSTATE]): | ||
... | ||
|
||
def __init__(self): | ||
if self.__subscription_tag: | ||
self._unsubscribe(self.__subscription_tag) | ||
self.__subscription_tag = self._subscribe(self) # noqa | ||
|
||
def __del__(self): | ||
if self.__subscription_tag: | ||
self._unsubscribe(self.__subscription_tag) | ||
|
||
@classmethod | ||
def _subscribe(cls, self: Type[DragAndDrop]) -> SubscriptionTag: | ||
subscription_tag = dpg.generate_uuid() | ||
cls.__subscribers[subscription_tag] = self | ||
return subscription_tag | ||
|
||
@classmethod | ||
def _unsubscribe(self, subscription_tag: SubscriptionTag): | ||
if subscription_tag in self.__subscribers: | ||
del self.__subscribers[subscription_tag] | ||
|
||
@classmethod | ||
def _DragEnter(cls, dataObject, keyState): | ||
for self in cls.__subscribers.values(): | ||
try: | ||
self.DragEnter(dataObject, keyState) # noqa | ||
except Exception: | ||
traceback.print_exc() | ||
|
||
@classmethod | ||
def _DragOver(cls, keyState): | ||
for self in cls.__subscribers.values(): | ||
try: | ||
self.DragOver(keyState) # noqa | ||
except Exception: | ||
traceback.print_exc() | ||
|
||
@classmethod | ||
def _DragLeave(cls): | ||
for self in cls.__subscribers.values(): | ||
try: | ||
self.DragLeave() # noqa | ||
except Exception: | ||
traceback.print_exc() | ||
|
||
@classmethod | ||
def _Drop(cls, dataObject, keyState): | ||
for self in cls.__subscribers.values(): | ||
try: | ||
self.Drop(dataObject, keyState) # noqa | ||
except Exception: | ||
traceback.print_exc() | ||
|
||
|
||
class _DragAndDropForFunctions(DragAndDrop): | ||
def DragEnter(self, dataObject, keyState): | ||
... | ||
|
||
def DragOver(self, keyState): | ||
... | ||
|
||
def DragLeave(self): | ||
... | ||
|
||
def Drop(self, dataObject, keyState): | ||
... | ||
|
||
|
||
def set_drag_enter(function: Callable[[DragAndDropDataObject, list[KEYSTATE]], Any] = None): | ||
if function is None: | ||
function = lambda *args, **kwargs: ... | ||
_DragAndDropForFunctions.DragEnter = function | ||
|
||
|
||
def set_drag_over(function: Callable[[list[KEYSTATE]], Any] = None): | ||
if function is None: | ||
function = lambda *args, **kwargs: ... | ||
_DragAndDropForFunctions.DragOver = function | ||
|
||
|
||
def set_drag_leave(function: Callable[[], Any] = None): | ||
if function is None: | ||
function = lambda *args, **kwargs: ... | ||
_DragAndDropForFunctions.DragLeave = function | ||
|
||
|
||
def set_drop(function: Callable[[DragAndDropDataObject, list[KEYSTATE]], Any] = None): | ||
if function is None: | ||
function = lambda *args, **kwargs: ... | ||
_DragAndDropForFunctions.Drop = function |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import ctypes | ||
import os | ||
import time | ||
|
||
import dearpygui.dearpygui as dpg | ||
import pythoncom | ||
import win32gui | ||
from win32com.server.policy import DesignatedWrapPolicy | ||
|
||
from . import tools | ||
from .main import DragAndDrop | ||
from .main import get_drop_effect | ||
|
||
hwnd = -1 | ||
|
||
|
||
class DropTarget(DesignatedWrapPolicy): | ||
_public_methods_ = ['DragEnter', 'DragOver', 'DragLeave', 'Drop'] | ||
_com_interfaces_ = [pythoncom.IID_IDropTarget] # noqa | ||
|
||
def __init__(self): # noqa | ||
self._wrap_(self) | ||
|
||
def DragEnter(self, dataObject, keyState, point, effect): | ||
ctypes.windll.user32.SetForegroundWindow(hwnd) | ||
DragAndDrop._DragEnter(tools.get_data_from_dataObject(dataObject), | ||
tools.key_state_to_keys_list(keyState)) | ||
return 0, get_drop_effect() | ||
# return winerror.S_OK, get_drop_effect() | ||
|
||
def DragOver(self, keyState, point, effect): | ||
DragAndDrop._DragOver(tools.key_state_to_keys_list(keyState)) | ||
return get_drop_effect() | ||
|
||
def DragLeave(self): | ||
DragAndDrop._DragLeave() | ||
|
||
def Drop(self, dataObject, keyState, point, effect): | ||
DragAndDrop._Drop(tools.get_data_from_dataObject(dataObject), | ||
tools.key_state_to_keys_list(keyState)) | ||
|
||
|
||
def setup(): | ||
global hwnd | ||
if hwnd != -1: | ||
return | ||
hwnd = 0 | ||
# Wait for initialization and appearance of the DPG window | ||
while dpg.get_frame_count() == 0: | ||
time.sleep(0.1) | ||
|
||
# Get the window hwnd from its own pid | ||
hwnd = tools.get_hwnd_from_pid(os.getpid()) | ||
|
||
pythoncom.OleInitialize() # noqa | ||
pythoncom.RegisterDragDrop( # noqa | ||
hwnd, | ||
pythoncom.WrapObject( # noqa | ||
DropTarget(), | ||
pythoncom.IID_IDropTarget, # noqa | ||
pythoncom.IID_IDropTarget # noqa | ||
) | ||
) | ||
win32gui.PumpMessages() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
from __future__ import annotations | ||
|
||
import ctypes.wintypes | ||
from ctypes import wintypes | ||
|
||
import pythoncom | ||
import pywintypes | ||
import win32con | ||
from win32comext.shell import shell | ||
|
||
from .main import DragAndDropDataObject | ||
from .main import KEYSTATE | ||
|
||
|
||
def get_data_from_dataObject(dataObject) -> DragAndDropDataObject: | ||
try: | ||
# Drag&Drop files | ||
_format = win32con.CF_HDROP, None, 1, -1, pythoncom.TYMED_HGLOBAL # noqa | ||
sm = dataObject.GetData(_format) | ||
except pywintypes.com_error: # noqa | ||
try: | ||
# Drag&Drop text | ||
_format = win32con.CF_TEXT, None, 1, -1, pythoncom.TYMED_HGLOBAL # noqa | ||
sm = dataObject.GetData(_format) | ||
# returns a string converted from bytes | ||
return sm.data.decode("utf-8") | ||
except pywintypes.com_error: # noqa | ||
# Drag&Drop unknown type | ||
# return nothing | ||
return None | ||
|
||
# Drag&Drop files | ||
num_files = shell.DragQueryFile(sm.data_handle, -1) | ||
files = [] | ||
for i in range(num_files): | ||
fpath = shell.DragQueryFile(sm.data_handle, i) | ||
files.append(fpath) | ||
# returns a list of file paths | ||
return files | ||
|
||
|
||
user32 = ctypes.windll.user32 | ||
WNDENUMPROC = ctypes.WINFUNCTYPE(wintypes.BOOL, | ||
wintypes.HWND, | ||
wintypes.LPARAM) | ||
user32.EnumWindows.argtypes = [WNDENUMPROC, | ||
wintypes.LPARAM] | ||
|
||
|
||
def get_hwnd_from_pid(pid: int) -> int | None: | ||
result = None | ||
|
||
def callback(hwnd, _): | ||
nonlocal result | ||
lpdw_PID = ctypes.c_ulong() | ||
user32.GetWindowThreadProcessId(hwnd, ctypes.byref(lpdw_PID)) | ||
hwnd_PID = lpdw_PID.value | ||
|
||
if hwnd_PID == pid: | ||
result = hwnd | ||
return False | ||
return True | ||
|
||
cb_worker = WNDENUMPROC(callback) | ||
user32.EnumWindows(cb_worker, 0) | ||
return result | ||
|
||
|
||
def key_state_to_keys_list(keyState: int) -> list[KEYSTATE]: | ||
# https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.drageventargs.keystate?view=windowsdesktop-7.0#remarks | ||
key_state_list = [] | ||
|
||
# conversion to byte string and reverse it | ||
keyState = '{0:06b}'.format(keyState)[::-1] | ||
if bool(int(keyState[0])): | ||
key_state_list.append(KEYSTATE.LEFT) | ||
if bool(int(keyState[1])): | ||
key_state_list.append(KEYSTATE.RIGHT) | ||
if bool(int(keyState[2])): | ||
key_state_list.append(KEYSTATE.SHIFT) | ||
if bool(int(keyState[3])): | ||
key_state_list.append(KEYSTATE.CTRL) | ||
if bool(int(keyState[4])): | ||
key_state_list.append(KEYSTATE.MIDDLE) | ||
if bool(int(keyState[5])): | ||
key_state_list.append(KEYSTATE.ALT) | ||
return key_state_list |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# Examples | ||
|
||
You can find several scenarios here, from simple to advanced, for using the library.<br> | ||
Below you can see what they look like and their brief description. | ||
|
||
## [Example №1: the simplest use case](example1.py) | ||
|
||
Probably the simplest and most used example of using. | ||
Drop the file into the program and our `drop` (change text) function will be called. | ||
![example_1](https://user-images.githubusercontent.com/46572469/210179706-f187b70b-6649-44d4-880b-a6b35a961d08.gif) | ||
|
||
## [Example №2: little interactivity](example2.py) | ||
|
||
An example with a little interactivity, in the form of a pop-up window that says `Drop!` | ||
(This is "similar" to the popup in Discord when you try to send a file by drag and drop). | ||
This one has more functions, but also seems to be nothing complicated. | ||
In `drag_enter` and `drag_leave` we hide and show the window respectively, and in `drop` we add hiding the window. | ||
![example_2](https://user-images.githubusercontent.com/46572469/210180323-1ab73614-0f35-49e8-b5e1-7c65c332e44b.gif) | ||
|
||
## [Example №3: more interactivity!](example3.py) | ||
|
||
In this example, you can only drop a file in the popup window. | ||
Added a function `drag_over` which checks the pointing to the window and, depending on the result, | ||
changes the theme of the window and the style of the cursor. | ||
![example_3](https://user-images.githubusercontent.com/46572469/210180650-b91c77d8-19da-4452-ad69-f8caa0201c32.gif) | ||
|
||
## [Example №4: keys usage](example4.py) | ||
|
||
Let's expand our example again, now the window theme depends on the key presses (CTRL, ALT, SHIFT). | ||
![example_4](https://user-images.githubusercontent.com/46572469/210181492-3a9bca4a-005a-4e5c-b8cf-4a39b041ef27.gif) | ||
|
||
## [Example №5: inheriting *DragAndDrop* or how to add an extra function to Drag And Drop](example4.py) | ||
|
||
Probably you need a lot of *DragAndDrop* functions when developing some applications, | ||
and only one function can be attached to `set_drop`. | ||
And there is a solution for this, namely to use the class *DragAndDrop*, or rather inherit it. | ||
When `__init__` is called in *DragAndDrop* (`super().__init__()`), | ||
this object (`self`) gets to the Drag-and-Drop functions queue, | ||
where certain functions will be called in the future, for example `{DragAndDrop_oject}.DragOver`. | ||
This example uses only `DragOver` and `Drop`, but also has `DragEnter` and `DragLeave` | ||
which correspond to the functions `.set_drag_enter` and `.set_drag_leave` respectively. | ||
I would also like to add that all functions `.set_*` are called first in the queue | ||
(if you `.initialize()` before the DragAndDrop classes). | ||
And the "function" of setting the cursor style will be called after the queue is finished | ||
(that is, the last called `.set_drop_effect` will be taken).<br> | ||
|
||
A little about the example: there are several "areas" in | ||
which you can drop a file and the text in them will change when it `drop` | ||
|
||
![example_5](https://user-images.githubusercontent.com/46572469/210182508-d2232d43-0df3-4531-a34b-e773f9000ef9.gif) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import dearpygui.dearpygui as dpg | ||
|
||
import DearPyGui_DragAndDrop as dpg_dnd | ||
|
||
dpg.create_context() | ||
dpg_dnd.initialize() | ||
dpg.create_viewport(title="Drag and drop example1", width=600, height=600) | ||
|
||
with dpg.window() as window: | ||
drop_text = dpg.add_text() | ||
drop_keys = dpg.add_text() | ||
dpg.set_primary_window(window, True) | ||
|
||
|
||
def drop(data, keys): | ||
dpg.set_value(drop_text, f'{data}') | ||
dpg.set_value(drop_keys, f'{keys}') | ||
|
||
|
||
dpg_dnd.set_drop(drop) | ||
|
||
dpg.setup_dearpygui() | ||
dpg.show_viewport() | ||
dpg.start_dearpygui() | ||
dpg.destroy_context() |
Oops, something went wrong.