ui替换
This commit is contained in:
parent
0860182b11
commit
497ecc8608
10
core/drag_drop/__init__.py
Normal file
10
core/drag_drop/__init__.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
"""
|
||||||
|
跨平台拖拽检测模块
|
||||||
|
|
||||||
|
提供统一的拖拽检测接口,支持Windows、Linux和macOS平台。
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .base_detector import BaseDragDetector
|
||||||
|
from .platform_detector import PlatformDragDetector
|
||||||
|
|
||||||
|
__all__ = ['BaseDragDetector', 'PlatformDragDetector']
|
||||||
120
core/drag_drop/base_detector.py
Normal file
120
core/drag_drop/base_detector.py
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
"""
|
||||||
|
拖拽检测器基类
|
||||||
|
|
||||||
|
定义所有平台拖拽检测器的通用接口。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import List, Optional, Callable
|
||||||
|
import threading
|
||||||
|
import queue
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
class BaseDragDetector(ABC):
|
||||||
|
"""拖拽检测器基类"""
|
||||||
|
|
||||||
|
def __init__(self, supported_formats: List[str] = None):
|
||||||
|
"""
|
||||||
|
初始化拖拽检测器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
supported_formats: 支持的文件格式列表,如 ['.gltf', '.glb', '.fbx']
|
||||||
|
"""
|
||||||
|
self.supported_formats = supported_formats or ['.gltf', '.glb', '.fbx', '.bam', '.egg', '.obj']
|
||||||
|
self.is_running = False
|
||||||
|
self.monitor_thread = None
|
||||||
|
self.file_queue = queue.Queue()
|
||||||
|
self.drop_callback: Optional[Callable[[List[str]], None]] = None
|
||||||
|
|
||||||
|
def set_drop_callback(self, callback: Callable[[List[str]], None]):
|
||||||
|
"""
|
||||||
|
设置文件拖拽回调函数
|
||||||
|
|
||||||
|
Args:
|
||||||
|
callback: 接收文件路径列表的回调函数
|
||||||
|
"""
|
||||||
|
self.drop_callback = callback
|
||||||
|
|
||||||
|
def start_monitoring(self):
|
||||||
|
"""开始监控拖拽事件"""
|
||||||
|
if not self.is_running:
|
||||||
|
self.is_running = True
|
||||||
|
self.monitor_thread = threading.Thread(target=self._monitor_loop, daemon=True)
|
||||||
|
self.monitor_thread.start()
|
||||||
|
|
||||||
|
def stop_monitoring(self):
|
||||||
|
"""停止监控拖拽事件"""
|
||||||
|
self.is_running = False
|
||||||
|
if self.monitor_thread:
|
||||||
|
self.monitor_thread.join(timeout=1.0)
|
||||||
|
|
||||||
|
def get_dropped_files(self) -> List[str]:
|
||||||
|
"""获取拖拽的文件列表"""
|
||||||
|
files = []
|
||||||
|
while not self.file_queue.empty():
|
||||||
|
try:
|
||||||
|
files.append(self.file_queue.get_nowait())
|
||||||
|
except queue.Empty:
|
||||||
|
break
|
||||||
|
return files
|
||||||
|
|
||||||
|
def add_dropped_file(self, file_path: str):
|
||||||
|
"""
|
||||||
|
添加拖拽的文件路径
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file_path: 拖拽的文件路径
|
||||||
|
"""
|
||||||
|
if self._is_supported_format(file_path):
|
||||||
|
self.file_queue.put(file_path)
|
||||||
|
if self.drop_callback:
|
||||||
|
self.drop_callback([file_path])
|
||||||
|
|
||||||
|
def _is_supported_format(self, file_path: str) -> bool:
|
||||||
|
"""检查文件格式是否支持"""
|
||||||
|
_, ext = os.path.splitext(file_path.lower())
|
||||||
|
return ext in self.supported_formats
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def _monitor_loop(self):
|
||||||
|
"""监控循环,由子类实现"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def is_supported(self) -> bool:
|
||||||
|
"""检查当前平台是否支持此检测器"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DragDetectorFactory:
|
||||||
|
"""拖拽检测器工厂类"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_detector(supported_formats: List[str] = None) -> BaseDragDetector:
|
||||||
|
"""
|
||||||
|
根据当前平台创建合适的拖拽检测器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
supported_formats: 支持的文件格式列表
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
适合当前平台的拖拽检测器实例
|
||||||
|
"""
|
||||||
|
system = platform.system().lower()
|
||||||
|
|
||||||
|
if system == 'windows':
|
||||||
|
from .windows_detector import WindowsDragDetector
|
||||||
|
return WindowsDragDetector(supported_formats)
|
||||||
|
elif system == 'linux':
|
||||||
|
from .linux_detector import LinuxDragDetector
|
||||||
|
return LinuxDragDetector(supported_formats)
|
||||||
|
elif system == 'darwin': # macOS
|
||||||
|
from .macos_detector import MacOSDragDetector
|
||||||
|
return MacOSDragDetector(supported_formats)
|
||||||
|
else:
|
||||||
|
# 降级到基础检测器
|
||||||
|
from .fallback_detector import FallbackDragDetector
|
||||||
|
return FallbackDragDetector(supported_formats)
|
||||||
83
core/drag_drop/fallback_detector.py
Normal file
83
core/drag_drop/fallback_detector.py
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
"""
|
||||||
|
降级拖拽检测器
|
||||||
|
|
||||||
|
当平台特定检测器不可用时的降级方案。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
from typing import List
|
||||||
|
from .base_detector import BaseDragDetector
|
||||||
|
|
||||||
|
|
||||||
|
class FallbackDragDetector(BaseDragDetector):
|
||||||
|
"""降级拖拽检测器"""
|
||||||
|
|
||||||
|
def __init__(self, supported_formats: List[str] = None):
|
||||||
|
"""
|
||||||
|
初始化降级检测器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
supported_formats: 支持的文件格式列表
|
||||||
|
"""
|
||||||
|
super().__init__(supported_formats)
|
||||||
|
self._watch_directory = os.path.expanduser("~/Desktop") # 监控桌面
|
||||||
|
self._known_files = set()
|
||||||
|
self._scan_interval = 0.5 # 扫描间隔(秒)
|
||||||
|
|
||||||
|
# 初始化已知文件列表
|
||||||
|
self._scan_known_files()
|
||||||
|
|
||||||
|
def _scan_known_files(self):
|
||||||
|
"""扫描已知文件"""
|
||||||
|
self._known_files.clear()
|
||||||
|
if os.path.exists(self._watch_directory):
|
||||||
|
for filename in os.listdir(self._watch_directory):
|
||||||
|
if self._is_supported_format(filename):
|
||||||
|
filepath = os.path.join(self._watch_directory, filename)
|
||||||
|
self._known_files.add(filepath)
|
||||||
|
|
||||||
|
def _monitor_loop(self):
|
||||||
|
"""监控循环 - 通过文件系统变化检测"""
|
||||||
|
while self.is_running:
|
||||||
|
try:
|
||||||
|
if os.path.exists(self._watch_directory):
|
||||||
|
current_files = set()
|
||||||
|
for filename in os.listdir(self._watch_directory):
|
||||||
|
if self._is_supported_format(filename):
|
||||||
|
filepath = os.path.join(self._watch_directory, filename)
|
||||||
|
current_files.add(filepath)
|
||||||
|
|
||||||
|
# 检测新文件
|
||||||
|
new_files = current_files - self._known_files
|
||||||
|
for filepath in new_files:
|
||||||
|
# 检查文件是否最近创建(1秒内)
|
||||||
|
try:
|
||||||
|
file_time = os.path.getmtime(filepath)
|
||||||
|
if time.time() - file_time < 1.0:
|
||||||
|
self.add_dropped_file(filepath)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self._known_files = current_files
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"降级检测器错误: {e}")
|
||||||
|
|
||||||
|
time.sleep(self._scan_interval)
|
||||||
|
|
||||||
|
def is_supported(self) -> bool:
|
||||||
|
"""降级检测器总是支持"""
|
||||||
|
return True
|
||||||
|
|
||||||
|
def set_watch_directory(self, directory: str):
|
||||||
|
"""
|
||||||
|
设置监控目录
|
||||||
|
|
||||||
|
Args:
|
||||||
|
directory: 要监控的目录路径
|
||||||
|
"""
|
||||||
|
if os.path.exists(directory):
|
||||||
|
self._watch_directory = directory
|
||||||
|
self._scan_known_files()
|
||||||
395
core/drag_drop/linux_detector.py
Normal file
395
core/drag_drop/linux_detector.py
Normal file
@ -0,0 +1,395 @@
|
|||||||
|
"""
|
||||||
|
Linux平台拖拽检测器
|
||||||
|
|
||||||
|
支持X11和Wayland窗口系统的拖拽检测。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
import subprocess
|
||||||
|
from typing import List, Optional
|
||||||
|
from .base_detector import BaseDragDetector
|
||||||
|
|
||||||
|
|
||||||
|
class LinuxDragDetector(BaseDragDetector):
|
||||||
|
"""Linux平台拖拽检测器"""
|
||||||
|
|
||||||
|
def __init__(self, supported_formats: List[str] = None):
|
||||||
|
"""
|
||||||
|
初始化Linux拖拽检测器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
supported_formats: 支持的文件格式列表
|
||||||
|
"""
|
||||||
|
super().__init__(supported_formats)
|
||||||
|
self._window_id = None
|
||||||
|
self._display = None
|
||||||
|
self._is_x11 = self._check_x11()
|
||||||
|
self._is_wayland = self._check_wayland()
|
||||||
|
self._xdg_active = self._check_xdg()
|
||||||
|
|
||||||
|
def _check_x11(self) -> bool:
|
||||||
|
"""检查是否使用X11"""
|
||||||
|
try:
|
||||||
|
# 检查DISPLAY环境变量
|
||||||
|
if os.environ.get('DISPLAY'):
|
||||||
|
# 尝试连接X11显示
|
||||||
|
result = subprocess.run(['xset', 'q'],
|
||||||
|
capture_output=True,
|
||||||
|
timeout=2)
|
||||||
|
return result.returncode == 0
|
||||||
|
except (subprocess.TimeoutExpired, FileNotFoundError):
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _check_wayland(self) -> bool:
|
||||||
|
"""检查是否使用Wayland"""
|
||||||
|
return os.environ.get('WAYLAND_DISPLAY') is not None
|
||||||
|
|
||||||
|
def _check_xdg(self) -> bool:
|
||||||
|
"""检查XDG桌面环境是否可用"""
|
||||||
|
try:
|
||||||
|
# 检查是否安装了xdg-user-dir
|
||||||
|
result = subprocess.run(['which', 'xdg-user-dir'],
|
||||||
|
capture_output=True)
|
||||||
|
return result.returncode == 0
|
||||||
|
except FileNotFoundError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _get_window_id(self) -> Optional[str]:
|
||||||
|
"""获取当前窗口ID"""
|
||||||
|
try:
|
||||||
|
# 使用xdotool获取活动窗口
|
||||||
|
result = subprocess.run(['xdotool', 'getactivewindow'],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=2)
|
||||||
|
if result.returncode == 0:
|
||||||
|
return result.stdout.strip()
|
||||||
|
except (subprocess.TimeoutExpired, FileNotFoundError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 备用方法:使用wmctrl
|
||||||
|
try:
|
||||||
|
result = subprocess.run(['wmctrl', '-l'],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=2)
|
||||||
|
if result.returncode == 0:
|
||||||
|
lines = result.stdout.strip().split('\n')
|
||||||
|
if lines:
|
||||||
|
# 获取第一个窗口的ID
|
||||||
|
return lines[0].split()[0]
|
||||||
|
except (subprocess.TimeoutExpired, FileNotFoundError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _monitor_x11_drag(self):
|
||||||
|
"""监控X11拖拽事件"""
|
||||||
|
try:
|
||||||
|
# 使用xev监控窗口事件
|
||||||
|
window_id = self._get_window_id()
|
||||||
|
if not window_id:
|
||||||
|
print("无法获取窗口ID,切换到文件监控")
|
||||||
|
self._monitor_file_drag()
|
||||||
|
return
|
||||||
|
|
||||||
|
cmd = ['xev', '-id', window_id, '-event', 'pointer']
|
||||||
|
process = subprocess.Popen(cmd,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
text=True)
|
||||||
|
|
||||||
|
print(f"开始监控X11窗口 {window_id} 的拖拽事件")
|
||||||
|
|
||||||
|
while self.is_running:
|
||||||
|
line = process.stdout.readline()
|
||||||
|
if not line:
|
||||||
|
break
|
||||||
|
|
||||||
|
# 检测拖拽相关事件
|
||||||
|
if any(keyword in line for keyword in ['DND', 'Xdnd', 'ClientMessage', 'SelectionNotify']):
|
||||||
|
# 尝试获取拖拽的文件
|
||||||
|
self._extract_x11_drag_files()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"X11拖拽监控错误: {e}")
|
||||||
|
print("切换到文件监控模式")
|
||||||
|
self._monitor_file_drag()
|
||||||
|
finally:
|
||||||
|
if 'process' in locals():
|
||||||
|
process.terminate()
|
||||||
|
|
||||||
|
def _extract_x11_drag_files(self):
|
||||||
|
"""从X11拖拽事件中提取文件路径"""
|
||||||
|
try:
|
||||||
|
# 使用xclip获取剪贴板内容(可能包含拖拽的文件)
|
||||||
|
result = subprocess.run(['xclip', '-selection', 'primary', '-o'],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=1)
|
||||||
|
if result.returncode == 0 and result.stdout.strip():
|
||||||
|
self._process_clipboard_content(result.stdout.strip())
|
||||||
|
|
||||||
|
except (subprocess.TimeoutExpired, FileNotFoundError):
|
||||||
|
# 尝试使用xsel作为备选
|
||||||
|
try:
|
||||||
|
result = subprocess.run(['xsel', '--primary', '--output'],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=1)
|
||||||
|
if result.returncode == 0 and result.stdout.strip():
|
||||||
|
self._process_clipboard_content(result.stdout.strip())
|
||||||
|
except (subprocess.TimeoutExpired, FileNotFoundError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _process_clipboard_content(self, content):
|
||||||
|
"""处理剪贴板内容,提取文件路径"""
|
||||||
|
# 检查是否包含文件路径
|
||||||
|
lines = content.split('\n')
|
||||||
|
for line in lines:
|
||||||
|
line = line.strip()
|
||||||
|
if line.startswith('file://'):
|
||||||
|
# URI格式
|
||||||
|
file_path = line[7:] # 移除 'file://' 前缀
|
||||||
|
file_path = file_path.replace('%20', ' ') # 解码空格
|
||||||
|
if self._is_supported_format(file_path):
|
||||||
|
self.add_dropped_file(file_path)
|
||||||
|
elif os.path.isabs(line) and self._is_supported_format(line):
|
||||||
|
# 绝对路径
|
||||||
|
self.add_dropped_file(line)
|
||||||
|
|
||||||
|
def _monitor_wayland_drag(self):
|
||||||
|
"""监控Wayland拖拽事件"""
|
||||||
|
try:
|
||||||
|
# 尝试使用wlrctl监控拖拽事件
|
||||||
|
if self._check_command_exists('wlrctl'):
|
||||||
|
self._monitor_wlrctl_drag()
|
||||||
|
else:
|
||||||
|
# 尝试使用wl-paste监控剪贴板
|
||||||
|
if self._check_command_exists('wl-paste'):
|
||||||
|
self._monitor_wayland_clipboard()
|
||||||
|
else:
|
||||||
|
print("Wayland拖拽监控工具不可用,切换到文件监控")
|
||||||
|
self._monitor_file_drag()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Wayland拖拽监控错误: {e}")
|
||||||
|
print("切换到文件监控模式")
|
||||||
|
self._monitor_file_drag()
|
||||||
|
|
||||||
|
def _check_command_exists(self, command):
|
||||||
|
"""检查命令是否存在"""
|
||||||
|
try:
|
||||||
|
result = subprocess.run(['which', command],
|
||||||
|
capture_output=True,
|
||||||
|
timeout=1)
|
||||||
|
return result.returncode == 0
|
||||||
|
except (subprocess.TimeoutExpired, FileNotFoundError):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _monitor_wlrctl_drag(self):
|
||||||
|
"""使用wlrctl监控拖拽事件"""
|
||||||
|
print("使用wlrctl监控Wayland拖拽事件")
|
||||||
|
try:
|
||||||
|
# wlrctl可能支持拖拽监控
|
||||||
|
process = subprocess.Popen(['wlrctl', 'drag'],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
text=True)
|
||||||
|
|
||||||
|
while self.is_running:
|
||||||
|
line = process.stdout.readline()
|
||||||
|
if not line:
|
||||||
|
break
|
||||||
|
|
||||||
|
# 解析wlrctl输出
|
||||||
|
if 'file://' in line or os.path.isabs(line.strip()):
|
||||||
|
self._process_drag_line(line.strip())
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"wlrctl监控失败: {e}")
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
if 'process' in locals():
|
||||||
|
process.terminate()
|
||||||
|
|
||||||
|
def _monitor_wayland_clipboard(self):
|
||||||
|
"""监控Wayland剪贴板变化"""
|
||||||
|
print("使用wl-paste监控Wayland剪贴板")
|
||||||
|
last_clipboard = ""
|
||||||
|
|
||||||
|
while self.is_running:
|
||||||
|
try:
|
||||||
|
# 获取当前剪贴板内容
|
||||||
|
result = subprocess.run(['wl-paste', '--type', 'text/uri-list'],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=1)
|
||||||
|
|
||||||
|
if result.returncode == 0:
|
||||||
|
current_clipboard = result.stdout.strip()
|
||||||
|
if current_clipboard and current_clipboard != last_clipboard:
|
||||||
|
last_clipboard = current_clipboard
|
||||||
|
self._process_clipboard_content(current_clipboard)
|
||||||
|
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Wayland剪贴板监控错误: {e}")
|
||||||
|
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
def _process_drag_line(self, line):
|
||||||
|
"""处理拖拽行内容"""
|
||||||
|
if line.startswith('file://'):
|
||||||
|
file_path = line[7:]
|
||||||
|
file_path = file_path.replace('%20', ' ')
|
||||||
|
if self._is_supported_format(file_path):
|
||||||
|
self.add_dropped_file(file_path)
|
||||||
|
elif os.path.isabs(line) and self._is_supported_format(line):
|
||||||
|
self.add_dropped_file(line)
|
||||||
|
|
||||||
|
def _get_user_directories(self):
|
||||||
|
"""获取用户常用目录"""
|
||||||
|
directories = []
|
||||||
|
|
||||||
|
# 获取桌面目录
|
||||||
|
desktop = os.path.expanduser("~/Desktop")
|
||||||
|
if os.path.exists(desktop):
|
||||||
|
directories.append(desktop)
|
||||||
|
|
||||||
|
# 获取下载目录
|
||||||
|
downloads = os.path.expanduser("~/Downloads")
|
||||||
|
if os.path.exists(downloads):
|
||||||
|
directories.append(downloads)
|
||||||
|
|
||||||
|
# 获取主目录
|
||||||
|
home = os.path.expanduser("~")
|
||||||
|
if os.path.exists(home):
|
||||||
|
directories.append(home)
|
||||||
|
|
||||||
|
# 获取临时目录
|
||||||
|
temp_dirs = ['/tmp', '/var/tmp']
|
||||||
|
for temp_dir in temp_dirs:
|
||||||
|
if os.path.exists(temp_dir):
|
||||||
|
directories.append(temp_dir)
|
||||||
|
|
||||||
|
return directories
|
||||||
|
|
||||||
|
def _monitor_file_drag(self):
|
||||||
|
"""通过文件系统变化监控拖拽"""
|
||||||
|
watch_dirs = self._get_user_directories()
|
||||||
|
known_files = {}
|
||||||
|
recent_files = {} # 用于跟踪最近添加的文件
|
||||||
|
|
||||||
|
# 初始化已知文件
|
||||||
|
for watch_dir in watch_dirs:
|
||||||
|
if os.path.exists(watch_dir):
|
||||||
|
for filename in os.listdir(watch_dir):
|
||||||
|
filepath = os.path.join(watch_dir, filename)
|
||||||
|
if self._is_supported_format(filepath):
|
||||||
|
try:
|
||||||
|
stat = os.stat(filepath)
|
||||||
|
known_files[filepath] = stat.st_mtime
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
print(f"开始监控目录: {watch_dirs}")
|
||||||
|
print(f"已知文件数量: {len(known_files)}")
|
||||||
|
|
||||||
|
while self.is_running:
|
||||||
|
try:
|
||||||
|
current_time = time.time()
|
||||||
|
|
||||||
|
for watch_dir in watch_dirs:
|
||||||
|
if not os.path.exists(watch_dir):
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 获取当前目录中的文件
|
||||||
|
current_files = {}
|
||||||
|
for filename in os.listdir(watch_dir):
|
||||||
|
filepath = os.path.join(watch_dir, filename)
|
||||||
|
if self._is_supported_format(filepath):
|
||||||
|
try:
|
||||||
|
stat = os.stat(filepath)
|
||||||
|
current_files[filepath] = stat.st_mtime
|
||||||
|
|
||||||
|
# 检查是否是新文件(最近5秒内创建)
|
||||||
|
if filepath not in known_files:
|
||||||
|
time_diff = current_time - stat.st_mtime
|
||||||
|
if time_diff < 5.0:
|
||||||
|
# 检查文件大小是否稳定(避免检测到正在下载的文件)
|
||||||
|
if self._is_file_stable(filepath):
|
||||||
|
print(f"检测到新文件: {filepath}")
|
||||||
|
recent_files[filepath] = current_time
|
||||||
|
self.add_dropped_file(filepath)
|
||||||
|
# 检查已知文件是否被更新
|
||||||
|
elif filepath in known_files:
|
||||||
|
if stat.st_mtime > known_files[filepath]:
|
||||||
|
time_diff = current_time - stat.st_mtime
|
||||||
|
if time_diff < 5.0 and filepath not in recent_files:
|
||||||
|
print(f"检测到文件更新: {filepath}")
|
||||||
|
recent_files[filepath] = current_time
|
||||||
|
self.add_dropped_file(filepath)
|
||||||
|
|
||||||
|
except OSError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 更新已知文件列表
|
||||||
|
known_files.update(current_files)
|
||||||
|
|
||||||
|
except PermissionError:
|
||||||
|
print(f"无权限访问目录: {watch_dir}")
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
print(f"监控目录 {watch_dir} 时出错: {e}")
|
||||||
|
|
||||||
|
# 清理过期的最近文件记录(超过30秒)
|
||||||
|
expired_files = [path for path, timestamp in recent_files.items()
|
||||||
|
if current_time - timestamp > 30]
|
||||||
|
for path in expired_files:
|
||||||
|
del recent_files[path]
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"文件拖拽监控错误: {e}")
|
||||||
|
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
def _is_file_stable(self, filepath, check_interval=0.5, max_checks=3):
|
||||||
|
"""检查文件大小是否稳定(避免检测到正在下载的文件)"""
|
||||||
|
try:
|
||||||
|
initial_size = os.path.getsize(filepath)
|
||||||
|
for i in range(max_checks):
|
||||||
|
time.sleep(check_interval)
|
||||||
|
current_size = os.path.getsize(filepath)
|
||||||
|
if current_size != initial_size:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _monitor_loop(self):
|
||||||
|
"""主监控循环"""
|
||||||
|
if self._is_x11:
|
||||||
|
try:
|
||||||
|
self._monitor_x11_drag()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"X11监控失败,切换到文件监控: {e}")
|
||||||
|
self._monitor_file_drag()
|
||||||
|
elif self._is_wayland:
|
||||||
|
try:
|
||||||
|
self._monitor_wayland_drag()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Wayland监控失败,切换到文件监控: {e}")
|
||||||
|
self._monitor_file_drag()
|
||||||
|
else:
|
||||||
|
# 降级到文件监控
|
||||||
|
self._monitor_file_drag()
|
||||||
|
|
||||||
|
def is_supported(self) -> bool:
|
||||||
|
"""检查Linux平台是否支持"""
|
||||||
|
return self._is_x11 or self._is_wayland or self._xdg_active
|
||||||
105
core/drag_drop/macos_detector.py
Normal file
105
core/drag_drop/macos_detector.py
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
"""
|
||||||
|
macOS平台拖拽检测器
|
||||||
|
|
||||||
|
使用macOS Cocoa API实现系统级拖拽检测。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
from typing import List, Optional
|
||||||
|
from .base_detector import BaseDragDetector
|
||||||
|
|
||||||
|
|
||||||
|
class MacOSDragDetector(BaseDragDetector):
|
||||||
|
"""macOS平台拖拽检测器"""
|
||||||
|
|
||||||
|
def __init__(self, supported_formats: List[str] = None):
|
||||||
|
"""
|
||||||
|
初始化macOS拖拽检测器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
supported_formats: 支持的文件格式列表
|
||||||
|
"""
|
||||||
|
super().__init__(supported_formats)
|
||||||
|
self._app_kit_available = self._check_app_kit()
|
||||||
|
self._cocoa_available = self._check_cocoa()
|
||||||
|
|
||||||
|
def _check_app_kit(self) -> bool:
|
||||||
|
"""检查AppKit是否可用"""
|
||||||
|
try:
|
||||||
|
import AppKit
|
||||||
|
return True
|
||||||
|
except ImportError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _check_cocoa(self) -> bool:
|
||||||
|
"""检查Cocoa是否可用"""
|
||||||
|
try:
|
||||||
|
import Cocoa
|
||||||
|
return True
|
||||||
|
except ImportError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _setup_cocoa_drag_drop(self) -> bool:
|
||||||
|
"""设置Cocoa拖拽接收"""
|
||||||
|
try:
|
||||||
|
if not self._app_kit_available:
|
||||||
|
return False
|
||||||
|
|
||||||
|
import AppKit
|
||||||
|
|
||||||
|
# 创建拖拽接收器
|
||||||
|
# 这里需要实现macOS的拖拽API调用
|
||||||
|
# 暂时返回False,需要进一步实现
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"设置Cocoa拖拽失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _monitor_file_drag(self):
|
||||||
|
"""通过文件系统变化监控拖拽"""
|
||||||
|
# 监控用户桌面和下载目录
|
||||||
|
desktop_path = os.path.expanduser("~/Desktop")
|
||||||
|
downloads_path = os.path.expanduser("~/Downloads")
|
||||||
|
|
||||||
|
watch_dirs = [desktop_path, downloads_path]
|
||||||
|
|
||||||
|
while self.is_running:
|
||||||
|
try:
|
||||||
|
current_time = time.time()
|
||||||
|
for watch_dir in watch_dirs:
|
||||||
|
if not os.path.exists(watch_dir):
|
||||||
|
continue
|
||||||
|
|
||||||
|
for filename in os.listdir(watch_dir):
|
||||||
|
filepath = os.path.join(watch_dir, filename)
|
||||||
|
try:
|
||||||
|
file_time = os.path.getmtime(filepath)
|
||||||
|
# 如果文件是最近1秒内创建的
|
||||||
|
if current_time - file_time < 1.0:
|
||||||
|
if self._is_supported_format(filepath):
|
||||||
|
self.add_dropped_file(filepath)
|
||||||
|
except OSError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"文件拖拽监控错误: {e}")
|
||||||
|
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
def _monitor_loop(self):
|
||||||
|
"""主监控循环"""
|
||||||
|
if self._app_kit_available and self._cocoa_available:
|
||||||
|
# 尝试设置真正的拖拽接收
|
||||||
|
if not self._setup_cocoa_drag_drop():
|
||||||
|
# 降级到文件系统监控
|
||||||
|
self._monitor_file_drag()
|
||||||
|
else:
|
||||||
|
# 降级到文件系统监控
|
||||||
|
self._monitor_file_drag()
|
||||||
|
|
||||||
|
def is_supported(self) -> bool:
|
||||||
|
"""检查macOS平台是否支持"""
|
||||||
|
return sys.platform.startswith('darwin')
|
||||||
66
core/drag_drop/platform_detector.py
Normal file
66
core/drag_drop/platform_detector.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
"""
|
||||||
|
平台拖拽检测器
|
||||||
|
|
||||||
|
自动选择合适的平台特定拖拽检测器。
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .base_detector import BaseDragDetector, DragDetectorFactory
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
|
class PlatformDragDetector:
|
||||||
|
"""平台拖拽检测器包装类"""
|
||||||
|
|
||||||
|
def __init__(self, supported_formats: List[str] = None):
|
||||||
|
"""
|
||||||
|
初始化平台拖拽检测器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
supported_formats: 支持的文件格式列表
|
||||||
|
"""
|
||||||
|
self.supported_formats = supported_formats or ['.gltf', '.glb', '.fbx', '.bam', '.egg', '.obj']
|
||||||
|
self.detector = DragDetectorFactory.create_detector(self.supported_formats)
|
||||||
|
self._is_monitoring = False
|
||||||
|
|
||||||
|
def set_drop_callback(self, callback):
|
||||||
|
"""设置拖拽回调函数"""
|
||||||
|
if self.detector:
|
||||||
|
self.detector.set_drop_callback(callback)
|
||||||
|
|
||||||
|
def start_monitoring(self):
|
||||||
|
"""开始监控"""
|
||||||
|
if self.detector and not self._is_monitoring:
|
||||||
|
self.detector.start_monitoring()
|
||||||
|
self._is_monitoring = True
|
||||||
|
|
||||||
|
def stop_monitoring(self):
|
||||||
|
"""停止监控"""
|
||||||
|
if self.detector and self._is_monitoring:
|
||||||
|
self.detector.stop_monitoring()
|
||||||
|
self._is_monitoring = False
|
||||||
|
|
||||||
|
def get_dropped_files(self):
|
||||||
|
"""获取拖拽的文件"""
|
||||||
|
if self.detector:
|
||||||
|
return self.detector.get_dropped_files()
|
||||||
|
return []
|
||||||
|
|
||||||
|
def is_supported(self) -> bool:
|
||||||
|
"""检查当前平台是否支持"""
|
||||||
|
return self.detector.is_supported() if self.detector else False
|
||||||
|
|
||||||
|
def add_dropped_file(self, file_path: str):
|
||||||
|
"""添加拖拽的文件路径"""
|
||||||
|
if self.detector:
|
||||||
|
self.detector.add_dropped_file(file_path)
|
||||||
|
|
||||||
|
def get_platform_info(self) -> dict:
|
||||||
|
"""获取平台信息"""
|
||||||
|
import platform
|
||||||
|
return {
|
||||||
|
'system': platform.system(),
|
||||||
|
'release': platform.release(),
|
||||||
|
'version': platform.version(),
|
||||||
|
'detector_type': type(self.detector).__name__,
|
||||||
|
'supported': self.is_supported()
|
||||||
|
}
|
||||||
233
core/drag_drop/simple_detector.py
Normal file
233
core/drag_drop/simple_detector.py
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
简化的拖拽检测器
|
||||||
|
|
||||||
|
使用文件系统监控和剪贴板检测实现拖拽功能
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import subprocess
|
||||||
|
import threading
|
||||||
|
from typing import List, Optional, Callable
|
||||||
|
from collections import deque
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
class SimpleDragDetector:
|
||||||
|
"""简化的拖拽检测器"""
|
||||||
|
|
||||||
|
def __init__(self, supported_formats: List[str]):
|
||||||
|
self.supported_formats = supported_formats
|
||||||
|
self.is_running = False
|
||||||
|
self.drop_callback = None
|
||||||
|
self.dropped_files = deque()
|
||||||
|
self.monitor_thread = None
|
||||||
|
self.last_clipboard = ""
|
||||||
|
|
||||||
|
def set_drop_callback(self, callback: Callable[[List[str]], None]):
|
||||||
|
"""设置拖拽回调函数"""
|
||||||
|
self.drop_callback = callback
|
||||||
|
|
||||||
|
def start_monitoring(self):
|
||||||
|
"""开始监控"""
|
||||||
|
if self.is_running:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.is_running = True
|
||||||
|
self.monitor_thread = threading.Thread(target=self._monitor_loop)
|
||||||
|
self.monitor_thread.daemon = True
|
||||||
|
self.monitor_thread.start()
|
||||||
|
print("✓ 简化拖拽监控已启动")
|
||||||
|
|
||||||
|
def stop_monitoring(self):
|
||||||
|
"""停止监控"""
|
||||||
|
self.is_running = False
|
||||||
|
if self.monitor_thread:
|
||||||
|
self.monitor_thread.join(timeout=1)
|
||||||
|
|
||||||
|
def get_dropped_files(self) -> List[str]:
|
||||||
|
"""获取拖拽的文件列表"""
|
||||||
|
files = list(self.dropped_files)
|
||||||
|
self.dropped_files.clear()
|
||||||
|
return files
|
||||||
|
|
||||||
|
def add_dropped_file(self, file_path: str):
|
||||||
|
"""添加拖拽的文件"""
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
file_ext = os.path.splitext(file_path)[1].lower()
|
||||||
|
if file_ext in self.supported_formats:
|
||||||
|
self.dropped_files.append(file_path)
|
||||||
|
print(f"检测到拖拽文件: {file_path}")
|
||||||
|
|
||||||
|
def _monitor_loop(self):
|
||||||
|
"""监控循环"""
|
||||||
|
watch_dirs = self._get_watch_directories()
|
||||||
|
known_files = set()
|
||||||
|
|
||||||
|
# 初始化已知文件
|
||||||
|
for watch_dir in watch_dirs:
|
||||||
|
self._scan_directory(watch_dir, known_files)
|
||||||
|
|
||||||
|
print(f"监控目录: {watch_dirs}")
|
||||||
|
print(f"已知文件数量: {len(known_files)}")
|
||||||
|
|
||||||
|
while self.is_running:
|
||||||
|
try:
|
||||||
|
current_time = time.time()
|
||||||
|
|
||||||
|
# 1. 检查剪贴板变化
|
||||||
|
self._check_clipboard()
|
||||||
|
|
||||||
|
# 2. 检查文件系统变化
|
||||||
|
for watch_dir in watch_dirs:
|
||||||
|
if not os.path.exists(watch_dir):
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
current_files = set()
|
||||||
|
for filename in os.listdir(watch_dir):
|
||||||
|
filepath = os.path.join(watch_dir, filename)
|
||||||
|
if self._is_supported_format(filepath):
|
||||||
|
try:
|
||||||
|
stat = os.stat(filepath)
|
||||||
|
current_files.add(filepath)
|
||||||
|
|
||||||
|
# 检查新文件
|
||||||
|
if filepath not in known_files:
|
||||||
|
time_diff = current_time - stat.st_mtime
|
||||||
|
if time_diff < 5.0: # 5秒内创建的文件
|
||||||
|
if self._is_file_stable(filepath):
|
||||||
|
print(f"检测到新文件: {filepath}")
|
||||||
|
self.add_dropped_file(filepath)
|
||||||
|
if self.drop_callback:
|
||||||
|
self.drop_callback([filepath])
|
||||||
|
|
||||||
|
except OSError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
known_files.update(current_files)
|
||||||
|
|
||||||
|
except PermissionError:
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
print(f"监控目录 {watch_dir} 时出错: {e}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"监控循环错误: {e}")
|
||||||
|
|
||||||
|
time.sleep(0.3) # 更频繁的检查
|
||||||
|
|
||||||
|
def _check_clipboard(self):
|
||||||
|
"""检查剪贴板变化"""
|
||||||
|
try:
|
||||||
|
# 检查剪贴板
|
||||||
|
result = subprocess.run(['xclip', '-selection', 'clipboard', '-o'],
|
||||||
|
capture_output=True, text=True, timeout=0.5)
|
||||||
|
if result.returncode == 0 and result.stdout.strip():
|
||||||
|
current_clipboard = result.stdout.strip()
|
||||||
|
if current_clipboard != self.last_clipboard:
|
||||||
|
self.last_clipboard = current_clipboard
|
||||||
|
files = self._parse_clipboard_content(current_clipboard)
|
||||||
|
if files:
|
||||||
|
print(f"剪贴板检测到文件: {files}")
|
||||||
|
for file_path in files:
|
||||||
|
self.add_dropped_file(file_path)
|
||||||
|
if self.drop_callback:
|
||||||
|
self.drop_callback(files)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
# 尝试使用xsel
|
||||||
|
try:
|
||||||
|
result = subprocess.run(['xsel', '--clipboard', '--output'],
|
||||||
|
capture_output=True, text=True, timeout=0.5)
|
||||||
|
if result.returncode == 0 and result.stdout.strip():
|
||||||
|
current_clipboard = result.stdout.strip()
|
||||||
|
if current_clipboard != self.last_clipboard:
|
||||||
|
self.last_clipboard = current_clipboard
|
||||||
|
files = self._parse_clipboard_content(current_clipboard)
|
||||||
|
if files:
|
||||||
|
for file_path in files:
|
||||||
|
self.add_dropped_file(file_path)
|
||||||
|
if self.drop_callback:
|
||||||
|
self.drop_callback(files)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _parse_clipboard_content(self, content: str) -> List[str]:
|
||||||
|
"""解析剪贴板内容"""
|
||||||
|
files = []
|
||||||
|
for line in content.split('\n'):
|
||||||
|
line = line.strip()
|
||||||
|
if line.startswith('file://'):
|
||||||
|
file_path = line[7:]
|
||||||
|
file_path = file_path.replace('%20', ' ')
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
files.append(file_path)
|
||||||
|
elif os.path.isabs(line) and os.path.exists(line):
|
||||||
|
files.append(line)
|
||||||
|
return files
|
||||||
|
|
||||||
|
def _get_watch_directories(self) -> List[str]:
|
||||||
|
"""获取监控目录"""
|
||||||
|
dirs = [
|
||||||
|
os.path.expanduser('~/Desktop'),
|
||||||
|
os.path.expanduser('~/Downloads'),
|
||||||
|
tempfile.gettempdir()
|
||||||
|
]
|
||||||
|
|
||||||
|
# 添加一些常见的拖拽目标目录
|
||||||
|
additional_dirs = [
|
||||||
|
os.path.expanduser('~/Documents'),
|
||||||
|
os.path.expanduser('~'),
|
||||||
|
'/tmp'
|
||||||
|
]
|
||||||
|
|
||||||
|
for dir_path in additional_dirs:
|
||||||
|
if os.path.exists(dir_path) and os.path.isdir(dir_path):
|
||||||
|
dirs.append(dir_path)
|
||||||
|
|
||||||
|
return [d for d in dirs if os.path.exists(d)]
|
||||||
|
|
||||||
|
def _scan_directory(self, directory: str, known_files: set):
|
||||||
|
"""扫描目录"""
|
||||||
|
try:
|
||||||
|
for filename in os.listdir(directory):
|
||||||
|
filepath = os.path.join(directory, filename)
|
||||||
|
if self._is_supported_format(filepath):
|
||||||
|
try:
|
||||||
|
known_files.add(filepath)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _is_supported_format(self, file_path: str) -> bool:
|
||||||
|
"""检查文件格式是否支持"""
|
||||||
|
if not os.path.isfile(file_path):
|
||||||
|
return False
|
||||||
|
file_ext = os.path.splitext(file_path)[1].lower()
|
||||||
|
return file_ext in self.supported_formats
|
||||||
|
|
||||||
|
def _is_file_stable(self, file_path: str, wait_time: float = 0.5) -> bool:
|
||||||
|
"""检查文件是否稳定"""
|
||||||
|
try:
|
||||||
|
initial_size = os.path.getsize(file_path)
|
||||||
|
time.sleep(wait_time)
|
||||||
|
current_size = os.path.getsize(file_path)
|
||||||
|
return initial_size == current_size
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def is_supported(self) -> bool:
|
||||||
|
"""检查是否支持"""
|
||||||
|
return True # 这个版本总是支持的
|
||||||
|
|
||||||
|
def get_platform_info(self) -> dict:
|
||||||
|
"""获取平台信息"""
|
||||||
|
return {
|
||||||
|
'system': 'Linux',
|
||||||
|
'detector_type': 'SimpleDragDetector',
|
||||||
|
'supported': True,
|
||||||
|
'method': 'File system + clipboard monitoring'
|
||||||
|
}
|
||||||
221
core/drag_drop/windows_detector.py
Normal file
221
core/drag_drop/windows_detector.py
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
"""
|
||||||
|
Windows平台拖拽检测器
|
||||||
|
|
||||||
|
使用Windows API实现系统级拖拽检测。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
from typing import List, Optional
|
||||||
|
from .base_detector import BaseDragDetector
|
||||||
|
|
||||||
|
|
||||||
|
class WindowsDragDetector(BaseDragDetector):
|
||||||
|
"""Windows平台拖拽检测器"""
|
||||||
|
|
||||||
|
def __init__(self, supported_formats: List[str] = None):
|
||||||
|
"""
|
||||||
|
初始化Windows拖拽检测器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
supported_formats: 支持的文件格式列表
|
||||||
|
"""
|
||||||
|
super().__init__(supported_formats)
|
||||||
|
self._hwnd = None
|
||||||
|
self._old_win_proc = None
|
||||||
|
self._is_com_initialized = False
|
||||||
|
self._ole_initialized = False
|
||||||
|
|
||||||
|
def _initialize_com(self) -> bool:
|
||||||
|
"""初始化COM"""
|
||||||
|
try:
|
||||||
|
import ctypes
|
||||||
|
from ctypes import wintypes
|
||||||
|
|
||||||
|
# 初始化COM
|
||||||
|
ctypes.windll.ole32.CoInitializeEx(None, 0) # COINIT_APARTMENTTHREADED
|
||||||
|
self._is_com_initialized = True
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"COM初始化失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _initialize_ole(self) -> bool:
|
||||||
|
"""初始化OLE"""
|
||||||
|
try:
|
||||||
|
import ctypes
|
||||||
|
from ctypes import wintypes
|
||||||
|
|
||||||
|
# 初始化OLE
|
||||||
|
ctypes.windll.ole32.OleInitialize(None)
|
||||||
|
self._ole_initialized = True
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"OLE初始化失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _get_window_handle(self) -> Optional[int]:
|
||||||
|
"""获取Panda3D窗口句柄"""
|
||||||
|
try:
|
||||||
|
# 这里需要获取Panda3D窗口的句柄
|
||||||
|
# 可能需要通过Panda3D的API获取
|
||||||
|
# 暂时返回None,需要进一步实现
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
print(f"获取窗口句柄失败: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _setup_drag_drop(self) -> bool:
|
||||||
|
"""设置拖拽接收"""
|
||||||
|
try:
|
||||||
|
import ctypes
|
||||||
|
from ctypes import wintypes
|
||||||
|
|
||||||
|
hwnd = self._get_window_handle()
|
||||||
|
if not hwnd:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 注册拖拽接收窗口
|
||||||
|
# 这里需要实现Windows拖拽API的调用
|
||||||
|
# 暂时返回False,需要进一步实现
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"设置拖拽接收失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _monitor_clipboard_drag(self):
|
||||||
|
"""通过剪贴板监控拖拽"""
|
||||||
|
try:
|
||||||
|
import ctypes
|
||||||
|
from ctypes import wintypes
|
||||||
|
|
||||||
|
user32 = ctypes.windll.user32
|
||||||
|
kernel32 = ctypes.windll.kernel32
|
||||||
|
|
||||||
|
# 监控剪贴板变化
|
||||||
|
while self.is_running:
|
||||||
|
# 检查剪贴板是否包含文件
|
||||||
|
if user32.OpenClipboard(None):
|
||||||
|
try:
|
||||||
|
# 检查是否是文件格式
|
||||||
|
format_id = 15 # CF_HDROP
|
||||||
|
if user32.IsClipboardFormatAvailable(format_id):
|
||||||
|
# 获取拖拽文件列表
|
||||||
|
h_drop = user32.GetClipboardData(format_id)
|
||||||
|
if h_drop:
|
||||||
|
files = self._parse_drop_files(h_drop)
|
||||||
|
for file_path in files:
|
||||||
|
self.add_dropped_file(file_path)
|
||||||
|
finally:
|
||||||
|
user32.CloseClipboard()
|
||||||
|
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"剪贴板监控错误: {e}")
|
||||||
|
|
||||||
|
def _parse_drop_files(self, h_drop) -> List[str]:
|
||||||
|
"""解析拖拽文件列表"""
|
||||||
|
try:
|
||||||
|
import ctypes
|
||||||
|
from ctypes import wintypes
|
||||||
|
|
||||||
|
kernel32 = ctypes.windll.kernel32
|
||||||
|
shell32 = ctypes.windll.shell32
|
||||||
|
|
||||||
|
# 获取文件数量
|
||||||
|
file_count = shell32.DragQueryFileW(h_drop, 0xFFFFFFFF, None, 0)
|
||||||
|
files = []
|
||||||
|
|
||||||
|
# 获取每个文件路径
|
||||||
|
for i in range(file_count):
|
||||||
|
# 获取文件路径长度
|
||||||
|
length = shell32.DragQueryFileW(h_drop, i, None, 0)
|
||||||
|
if length > 0:
|
||||||
|
# 分配缓冲区
|
||||||
|
buffer = ctypes.create_unicode_buffer(length + 1)
|
||||||
|
# 获取文件路径
|
||||||
|
shell32.DragQueryFileW(h_drop, i, buffer, length + 1)
|
||||||
|
files.append(buffer.value)
|
||||||
|
|
||||||
|
return files
|
||||||
|
except Exception as e:
|
||||||
|
print(f"解析拖拽文件失败: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _monitor_file_drag(self):
|
||||||
|
"""通过文件系统变化监控拖拽"""
|
||||||
|
# 监控用户桌面和下载目录
|
||||||
|
import winreg
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 获取桌面路径
|
||||||
|
key = winreg.OpenKey(winreg.HKEY_CURRENT_USER,
|
||||||
|
r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders")
|
||||||
|
desktop_path = winreg.QueryValueEx(key, "Desktop")[0]
|
||||||
|
downloads_path = winreg.QueryValueEx(key, "{374DE290-123F-4565-9164-39C4925E467B}")[0] # 下载文件夹
|
||||||
|
winreg.CloseKey(key)
|
||||||
|
except Exception:
|
||||||
|
# 降级到默认路径
|
||||||
|
desktop_path = os.path.expanduser("~/Desktop")
|
||||||
|
downloads_path = os.path.expanduser("~/Downloads")
|
||||||
|
|
||||||
|
watch_dirs = [desktop_path, downloads_path]
|
||||||
|
|
||||||
|
while self.is_running:
|
||||||
|
try:
|
||||||
|
current_time = time.time()
|
||||||
|
for watch_dir in watch_dirs:
|
||||||
|
if not os.path.exists(watch_dir):
|
||||||
|
continue
|
||||||
|
|
||||||
|
for filename in os.listdir(watch_dir):
|
||||||
|
filepath = os.path.join(watch_dir, filename)
|
||||||
|
try:
|
||||||
|
file_time = os.path.getmtime(filepath)
|
||||||
|
# 如果文件是最近1秒内创建的
|
||||||
|
if current_time - file_time < 1.0:
|
||||||
|
if self._is_supported_format(filepath):
|
||||||
|
self.add_dropped_file(filepath)
|
||||||
|
except OSError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"文件拖拽监控错误: {e}")
|
||||||
|
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
def _monitor_loop(self):
|
||||||
|
"""主监控循环"""
|
||||||
|
# 尝试初始化COM和OLE
|
||||||
|
com_ready = self._initialize_com()
|
||||||
|
ole_ready = self._initialize_ole()
|
||||||
|
|
||||||
|
if com_ready and ole_ready:
|
||||||
|
# 尝试设置真正的拖拽接收
|
||||||
|
if self._setup_drag_drop():
|
||||||
|
# 使用Windows API拖拽检测
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# 降级到剪贴板监控
|
||||||
|
self._monitor_clipboard_drag()
|
||||||
|
else:
|
||||||
|
# 降级到文件系统监控
|
||||||
|
self._monitor_file_drag()
|
||||||
|
|
||||||
|
# 清理COM和OLE
|
||||||
|
try:
|
||||||
|
if self._ole_initialized:
|
||||||
|
import ctypes
|
||||||
|
ctypes.windll.ole32.OleUninitialize()
|
||||||
|
if self._is_com_initialized:
|
||||||
|
import ctypes
|
||||||
|
ctypes.windll.ole32.CoUninitialize()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def is_supported(self) -> bool:
|
||||||
|
"""检查Windows平台是否支持"""
|
||||||
|
return sys.platform.startswith('win')
|
||||||
311
core/drag_drop/x11_drag_receiver.py
Normal file
311
core/drag_drop/x11_drag_receiver.py
Normal file
@ -0,0 +1,311 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
X11窗口拖拽事件接收器
|
||||||
|
|
||||||
|
使用X11的Xdnd协议实现真正的窗口拖拽功能
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import subprocess
|
||||||
|
import threading
|
||||||
|
from typing import List, Optional, Callable
|
||||||
|
from collections import deque
|
||||||
|
|
||||||
|
class X11DragReceiver:
|
||||||
|
"""X11窗口拖拽事件接收器"""
|
||||||
|
|
||||||
|
def __init__(self, supported_formats: List[str]):
|
||||||
|
self.supported_formats = supported_formats
|
||||||
|
self.window_id = None
|
||||||
|
self.is_running = False
|
||||||
|
self.drop_callback = None
|
||||||
|
self.dropped_files = deque()
|
||||||
|
self.monitor_thread = None
|
||||||
|
|
||||||
|
# X11相关
|
||||||
|
self.x11_display = os.environ.get('DISPLAY', ':0')
|
||||||
|
self.xdnd_aware = True # 标记窗口支持拖拽
|
||||||
|
|
||||||
|
def set_window(self, window_id: int):
|
||||||
|
"""设置要监控的窗口ID"""
|
||||||
|
self.window_id = window_id
|
||||||
|
print(f"设置拖拽监控窗口: {window_id}")
|
||||||
|
|
||||||
|
def set_drop_callback(self, callback: Callable[[List[str]], None]):
|
||||||
|
"""设置拖拽回调函数"""
|
||||||
|
self.drop_callback = callback
|
||||||
|
|
||||||
|
def start_monitoring(self):
|
||||||
|
"""开始监控拖拽事件"""
|
||||||
|
if self.is_running:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.is_running = True
|
||||||
|
self.monitor_thread = threading.Thread(target=self._monitor_drag_events)
|
||||||
|
self.monitor_thread.daemon = True
|
||||||
|
self.monitor_thread.start()
|
||||||
|
print("X11拖拽监控已启动")
|
||||||
|
|
||||||
|
def stop_monitoring(self):
|
||||||
|
"""停止监控"""
|
||||||
|
self.is_running = False
|
||||||
|
if self.monitor_thread:
|
||||||
|
self.monitor_thread.join(timeout=1)
|
||||||
|
|
||||||
|
def get_dropped_files(self) -> List[str]:
|
||||||
|
"""获取拖拽的文件列表"""
|
||||||
|
files = list(self.dropped_files)
|
||||||
|
self.dropped_files.clear()
|
||||||
|
return files
|
||||||
|
|
||||||
|
def add_dropped_file(self, file_path: str):
|
||||||
|
"""添加拖拽的文件"""
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
file_ext = os.path.splitext(file_path)[1].lower()
|
||||||
|
if file_ext in self.supported_formats:
|
||||||
|
self.dropped_files.append(file_path)
|
||||||
|
print(f"检测到拖拽文件: {file_path}")
|
||||||
|
|
||||||
|
def _monitor_drag_events(self):
|
||||||
|
"""监控拖拽事件"""
|
||||||
|
if not self.window_id:
|
||||||
|
print("窗口ID未设置,无法监控拖拽事件")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 首先注册窗口为拖拽目标
|
||||||
|
self._register_drag_target()
|
||||||
|
|
||||||
|
# 使用xev监控窗口事件
|
||||||
|
cmd = ['xev', '-id', str(self.window_id), '-event', 'dnd']
|
||||||
|
process = subprocess.Popen(cmd,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
text=True)
|
||||||
|
|
||||||
|
print(f"开始监控窗口 {self.window_id} 的拖拽事件")
|
||||||
|
|
||||||
|
while self.is_running:
|
||||||
|
line = process.stdout.readline()
|
||||||
|
if not line:
|
||||||
|
break
|
||||||
|
|
||||||
|
# 解析拖拽事件
|
||||||
|
if 'XdndEnter' in line:
|
||||||
|
self._handle_drag_enter(line)
|
||||||
|
elif 'XdndPosition' in line:
|
||||||
|
self._handle_drag_position(line)
|
||||||
|
elif 'XdndDrop' in line:
|
||||||
|
self._handle_drag_drop(line)
|
||||||
|
elif 'XdndLeave' in line:
|
||||||
|
self._handle_drag_leave(line)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"拖拽事件监控错误: {e}")
|
||||||
|
# 降级到文件监控
|
||||||
|
self._fallback_to_file_monitor()
|
||||||
|
finally:
|
||||||
|
if 'process' in locals():
|
||||||
|
process.terminate()
|
||||||
|
|
||||||
|
def _register_drag_target(self):
|
||||||
|
"""注册窗口为拖拽目标"""
|
||||||
|
try:
|
||||||
|
# 使用xprop设置窗口属性,标记支持拖拽
|
||||||
|
cmd = ['xprop', '-id', str(self.window_id), '-f', '_NET_WM_WINDOW_TYPE', '32a',
|
||||||
|
'-set', '_NET_WM_WINDOW_TYPE', '_NET_WM_WINDOW_TYPE_NORMAL']
|
||||||
|
subprocess.run(cmd, capture_output=True, timeout=2)
|
||||||
|
|
||||||
|
# 设置XdndAware属性
|
||||||
|
cmd = ['xprop', '-id', str(self.window_id), '-f', 'XdndAware', '32c',
|
||||||
|
'-set', 'XdndAware', '5']
|
||||||
|
subprocess.run(cmd, capture_output=True, timeout=2)
|
||||||
|
|
||||||
|
print(f"窗口 {self.window_id} 已注册为拖拽目标")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"注册拖拽目标失败: {e}")
|
||||||
|
|
||||||
|
def _handle_drag_enter(self, event_line: str):
|
||||||
|
"""处理拖拽进入事件"""
|
||||||
|
print("检测到拖拽进入")
|
||||||
|
|
||||||
|
def _handle_drag_position(self, event_line: str):
|
||||||
|
"""处理拖拽位置事件"""
|
||||||
|
# 可以在这里更新拖拽位置显示
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _handle_drag_drop(self, event_line: str):
|
||||||
|
"""处理拖拽释放事件"""
|
||||||
|
print("检测到拖拽释放")
|
||||||
|
|
||||||
|
# 尝试获取拖拽的文件
|
||||||
|
files = self._extract_dropped_files()
|
||||||
|
if files:
|
||||||
|
for file_path in files:
|
||||||
|
self.add_dropped_file(file_path)
|
||||||
|
|
||||||
|
# 调用回调函数
|
||||||
|
if self.drop_callback:
|
||||||
|
self.drop_callback(list(self.dropped_files))
|
||||||
|
|
||||||
|
def _handle_drag_leave(self, event_line: str):
|
||||||
|
"""处理拖拽离开事件"""
|
||||||
|
print("拖拽离开窗口")
|
||||||
|
|
||||||
|
def _extract_dropped_files(self) -> List[str]:
|
||||||
|
"""提取拖拽的文件路径"""
|
||||||
|
files = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 方法1:尝试从剪贴板获取
|
||||||
|
result = subprocess.run(['xclip', '-selection', 'clipboard', '-o'],
|
||||||
|
capture_output=True, text=True, timeout=1)
|
||||||
|
if result.returncode == 0 and result.stdout.strip():
|
||||||
|
files.extend(self._parse_uri_list(result.stdout.strip()))
|
||||||
|
|
||||||
|
# 方法2:尝试从主选择获取
|
||||||
|
result = subprocess.run(['xclip', '-selection', 'primary', '-o'],
|
||||||
|
capture_output=True, text=True, timeout=1)
|
||||||
|
if result.returncode == 0 and result.stdout.strip():
|
||||||
|
files.extend(self._parse_uri_list(result.stdout.strip()))
|
||||||
|
|
||||||
|
# 方法3:使用xsel作为备选
|
||||||
|
if not files:
|
||||||
|
result = subprocess.run(['xsel', '--clipboard', '--output'],
|
||||||
|
capture_output=True, text=True, timeout=1)
|
||||||
|
if result.returncode == 0 and result.stdout.strip():
|
||||||
|
files.extend(self._parse_uri_list(result.stdout.strip()))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"提取拖拽文件失败: {e}")
|
||||||
|
|
||||||
|
return files
|
||||||
|
|
||||||
|
def _parse_uri_list(self, uri_list: str) -> List[str]:
|
||||||
|
"""解析URI列表,提取文件路径"""
|
||||||
|
files = []
|
||||||
|
|
||||||
|
for line in uri_list.split('\n'):
|
||||||
|
line = line.strip()
|
||||||
|
if not line or line.startswith('#'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if line.startswith('file://'):
|
||||||
|
# URI格式
|
||||||
|
file_path = line[7:] # 移除 'file://' 前缀
|
||||||
|
file_path = file_path.replace('%20', ' ') # 解码空格
|
||||||
|
file_path = file_path.strip()
|
||||||
|
|
||||||
|
# 移除可能的回车符
|
||||||
|
if file_path.endswith('\r'):
|
||||||
|
file_path = file_path[:-1]
|
||||||
|
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
files.append(file_path)
|
||||||
|
elif os.path.isabs(line):
|
||||||
|
# 绝对路径
|
||||||
|
if os.path.exists(line):
|
||||||
|
files.append(line)
|
||||||
|
|
||||||
|
return files
|
||||||
|
|
||||||
|
def _fallback_to_file_monitor(self):
|
||||||
|
"""降级到文件监控"""
|
||||||
|
print("降级到文件监控模式")
|
||||||
|
|
||||||
|
# 监控桌面和下载目录
|
||||||
|
watch_dirs = [
|
||||||
|
os.path.expanduser('~/Desktop'),
|
||||||
|
os.path.expanduser('~/Downloads'),
|
||||||
|
'/tmp'
|
||||||
|
]
|
||||||
|
|
||||||
|
known_files = set()
|
||||||
|
|
||||||
|
# 初始化已知文件
|
||||||
|
for watch_dir in watch_dirs:
|
||||||
|
if os.path.exists(watch_dir):
|
||||||
|
for filename in os.listdir(watch_dir):
|
||||||
|
filepath = os.path.join(watch_dir, filename)
|
||||||
|
if self._is_supported_format(filepath):
|
||||||
|
try:
|
||||||
|
known_files.add(filepath)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
while self.is_running:
|
||||||
|
try:
|
||||||
|
current_time = time.time()
|
||||||
|
|
||||||
|
for watch_dir in watch_dirs:
|
||||||
|
if not os.path.exists(watch_dir):
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
for filename in os.listdir(watch_dir):
|
||||||
|
filepath = os.path.join(watch_dir, filename)
|
||||||
|
if self._is_supported_format(filepath):
|
||||||
|
try:
|
||||||
|
file_time = os.path.getmtime(filepath)
|
||||||
|
|
||||||
|
# 检查是否是新文件(最近5秒内创建)
|
||||||
|
if filepath not in known_files and current_time - file_time < 5.0:
|
||||||
|
print(f"检测到新文件: {filepath}")
|
||||||
|
self.add_dropped_file(filepath)
|
||||||
|
|
||||||
|
# 调用回调函数
|
||||||
|
if self.drop_callback:
|
||||||
|
self.drop_callback([filepath])
|
||||||
|
|
||||||
|
known_files.add(filepath)
|
||||||
|
|
||||||
|
except OSError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
except PermissionError:
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
print(f"监控目录 {watch_dir} 时出错: {e}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"文件监控错误: {e}")
|
||||||
|
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
def _is_supported_format(self, file_path: str) -> bool:
|
||||||
|
"""检查文件格式是否支持"""
|
||||||
|
if not os.path.isfile(file_path):
|
||||||
|
return False
|
||||||
|
|
||||||
|
file_ext = os.path.splitext(file_path)[1].lower()
|
||||||
|
return file_ext in self.supported_formats
|
||||||
|
|
||||||
|
def is_supported(self) -> bool:
|
||||||
|
"""检查是否支持拖拽功能"""
|
||||||
|
# 检查X11环境
|
||||||
|
if not os.environ.get('DISPLAY'):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 检查必要工具
|
||||||
|
required_tools = ['xev', 'xprop', 'xclip']
|
||||||
|
for tool in required_tools:
|
||||||
|
try:
|
||||||
|
subprocess.run(['which', tool], capture_output=True, check=True)
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
print(f"缺少必要工具: {tool}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_platform_info(self) -> dict:
|
||||||
|
"""获取平台信息"""
|
||||||
|
return {
|
||||||
|
'system': 'Linux',
|
||||||
|
'detector_type': 'X11DragReceiver',
|
||||||
|
'supported': self.is_supported(),
|
||||||
|
'display': self.x11_display,
|
||||||
|
'window_id': self.window_id
|
||||||
|
}
|
||||||
373
core/resource_manager.py
Normal file
373
core/resource_manager.py
Normal file
@ -0,0 +1,373 @@
|
|||||||
|
"""
|
||||||
|
资源管理器模块 - ImGui版本
|
||||||
|
提供文件浏览、图标显示、右键菜单等功能
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import platform
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, List, Optional, Tuple, Set
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceManager:
|
||||||
|
"""ImGui资源管理器类"""
|
||||||
|
|
||||||
|
def __init__(self, world):
|
||||||
|
self.world = world
|
||||||
|
self.project_root = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
# 当前浏览路径,默认从Resources目录开始
|
||||||
|
self.current_path = self.project_root / "Resources"
|
||||||
|
if not self.current_path.exists():
|
||||||
|
self.current_path = self.project_root
|
||||||
|
|
||||||
|
# 历史记录,用于前进后退导航
|
||||||
|
self.navigation_history: List[Path] = [self.current_path]
|
||||||
|
self.history_index = 0
|
||||||
|
|
||||||
|
# 文件选择状态
|
||||||
|
self.selected_files: Set[Path] = set()
|
||||||
|
self.focused_file: Optional[Path] = None
|
||||||
|
|
||||||
|
# 右键菜单状态
|
||||||
|
self.show_context_menu = False
|
||||||
|
self.context_menu_file: Optional[Path] = None
|
||||||
|
self.context_menu_position = (0, 0)
|
||||||
|
|
||||||
|
# 搜索和过滤
|
||||||
|
self.search_filter = ""
|
||||||
|
self.show_hidden_files = False
|
||||||
|
|
||||||
|
# 文件系统监控
|
||||||
|
self.last_refresh_time = 0
|
||||||
|
self.refresh_interval = 1.0 # 秒
|
||||||
|
self.auto_refresh_enabled = True
|
||||||
|
self.last_directory_content = {} # 缓存目录内容,用于检测变化
|
||||||
|
|
||||||
|
# 拖拽状态
|
||||||
|
self.dragged_files: List[Path] = []
|
||||||
|
|
||||||
|
# 展开/折叠状态
|
||||||
|
self.expanded_directories: Set[Path] = set()
|
||||||
|
|
||||||
|
# 文件图标映射(Unicode Emoji)
|
||||||
|
self._init_icon_map()
|
||||||
|
|
||||||
|
def _init_icon_map(self):
|
||||||
|
"""初始化文件图标映射"""
|
||||||
|
self.icon_map = {
|
||||||
|
# 编程语言文件
|
||||||
|
'.py': '🐍', # Python文件
|
||||||
|
'.js': '⚡', # JavaScript文件
|
||||||
|
'.ts': '⚡', # TypeScript文件
|
||||||
|
'.html': '🌐', # HTML文件
|
||||||
|
'.css': '🎨', # CSS文件
|
||||||
|
'.json': '📋', # JSON文件
|
||||||
|
'.xml': '📄', # XML文件
|
||||||
|
'.yaml': '📄', # YAML文件
|
||||||
|
'.yml': '📄', # YAML文件
|
||||||
|
'.md': '📝', # Markdown文档
|
||||||
|
|
||||||
|
# 3D模型文件
|
||||||
|
'.fbx': '🎭', # FBX模型文件
|
||||||
|
'.obj': '🎭', # OBJ模型文件
|
||||||
|
'.gltf': '🎭', # glTF模型
|
||||||
|
'.glb': '🎭', # glTF二进制模型
|
||||||
|
'.bam': '🎭', # BAM模型文件
|
||||||
|
'.egg': '🎭', # EGG模型文件
|
||||||
|
'.dae': '🎭', # Collada模型
|
||||||
|
'.3ds': '🎭', # 3DS模型
|
||||||
|
'.blend': '🎭', # Blender文件
|
||||||
|
|
||||||
|
# 图像文件
|
||||||
|
'.jpg': '🖼️', # JPEG图像
|
||||||
|
'.jpeg': '🖼️', # JPEG图像
|
||||||
|
'.png': '🖼️', # PNG图像
|
||||||
|
'.gif': '🖼️', # GIF图像
|
||||||
|
'.bmp': '🖼️', # BMP图像
|
||||||
|
'.tga': '🖼️', # TGA图像
|
||||||
|
'.tiff': '🖼️', # TIFF图像
|
||||||
|
'.webp': '🖼️', # WebP图像
|
||||||
|
'.svg': '🖼️', # SVG图像
|
||||||
|
'.ico': '🖼️', # ICO图标
|
||||||
|
|
||||||
|
# 音频文件
|
||||||
|
'.mp3': '🎵', # MP3音频
|
||||||
|
'.wav': '🎵', # WAV音频
|
||||||
|
'.ogg': '🎵', # OGG音频
|
||||||
|
'.flac': '🎵', # FLAC音频
|
||||||
|
'.m4a': '🎵', # M4A音频
|
||||||
|
|
||||||
|
# 视频文件
|
||||||
|
'.mp4': '🎬', # MP4视频
|
||||||
|
'.avi': '🎬', # AVI视频
|
||||||
|
'.mkv': '🎬', # MKV视频
|
||||||
|
'.mov': '🎬', # MOV视频
|
||||||
|
'.wmv': '🎬', # WMV视频
|
||||||
|
'.flv': '🎬', # FLV视频
|
||||||
|
|
||||||
|
# 文档文件
|
||||||
|
'.txt': '📄', # 纯文本文件
|
||||||
|
'.pdf': '📕', # PDF文档
|
||||||
|
'.doc': '📘', # Word文档
|
||||||
|
'.docx': '📘', # Word文档
|
||||||
|
'.xls': '📗', # Excel表格
|
||||||
|
'.xlsx': '📗', # Excel表格
|
||||||
|
'.ppt': '📙', # PowerPoint演示
|
||||||
|
'.pptx': '📙', # PowerPoint演示
|
||||||
|
|
||||||
|
# 压缩文件
|
||||||
|
'.zip': '📦', # ZIP压缩包
|
||||||
|
'.rar': '📦', # RAR压缩包
|
||||||
|
'.7z': '📦', # 7Z压缩包
|
||||||
|
'.tar': '📦', # TAR压缩包
|
||||||
|
'.gz': '📦', # GZ压缩包
|
||||||
|
|
||||||
|
# 配置文件
|
||||||
|
'.ini': '⚙️', # INI配置文件
|
||||||
|
'.cfg': '⚙️', # CFG配置文件
|
||||||
|
'.conf': '⚙️', # CONF配置文件
|
||||||
|
'.toml': '⚙️', # TOML配置文件
|
||||||
|
|
||||||
|
# 字体文件
|
||||||
|
'.ttf': '🔤', # TrueType字体
|
||||||
|
'.otf': '🔤', # OpenType字体
|
||||||
|
'.woff': '🔤', # WOFF字体
|
||||||
|
'.woff2': '🔤', # WOFF2字体
|
||||||
|
|
||||||
|
# 默认图标
|
||||||
|
'default': '📄', # 默认文件图标
|
||||||
|
'folder': '📁', # 文件夹图标
|
||||||
|
'folder_open': '📂', # 打开的文件夹图标
|
||||||
|
'drive': '💾', # 驱动器图标
|
||||||
|
'home': '🏠', # 主目录图标
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_file_icon(self, filename: str, is_folder: bool = False) -> str:
|
||||||
|
"""根据文件名获取图标"""
|
||||||
|
if is_folder:
|
||||||
|
return self.icon_map['folder']
|
||||||
|
|
||||||
|
ext = Path(filename).suffix.lower()
|
||||||
|
return self.icon_map.get(ext, self.icon_map['default'])
|
||||||
|
|
||||||
|
def get_directory_contents(self, path: Path) -> Tuple[List[Path], List[Path]]:
|
||||||
|
"""获取目录内容,返回(目录列表, 文件列表)"""
|
||||||
|
if not path.exists() or not path.is_dir():
|
||||||
|
return [], []
|
||||||
|
|
||||||
|
dirs = []
|
||||||
|
files = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
for item in path.iterdir():
|
||||||
|
# 跳过隐藏文件(除非启用显示隐藏文件)
|
||||||
|
if not self.show_hidden_files and item.name.startswith('.'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if item.is_dir():
|
||||||
|
dirs.append(item)
|
||||||
|
else:
|
||||||
|
files.append(item)
|
||||||
|
|
||||||
|
# 排序:目录和文件分别按名称排序
|
||||||
|
dirs.sort(key=lambda x: x.name.lower())
|
||||||
|
files.sort(key=lambda x: x.name.lower())
|
||||||
|
|
||||||
|
except PermissionError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return dirs, files
|
||||||
|
|
||||||
|
def navigate_to(self, path: Path):
|
||||||
|
"""导航到指定路径"""
|
||||||
|
if path.exists() and path.is_dir():
|
||||||
|
self.current_path = path.resolve()
|
||||||
|
|
||||||
|
# 更新导航历史
|
||||||
|
if self.history_index < len(self.navigation_history) - 1:
|
||||||
|
# 如果不在历史末尾,截断后面的历史
|
||||||
|
self.navigation_history = self.navigation_history[:self.history_index + 1]
|
||||||
|
|
||||||
|
self.navigation_history.append(self.current_path)
|
||||||
|
self.history_index += 1
|
||||||
|
|
||||||
|
# 清除选择状态
|
||||||
|
self.selected_files.clear()
|
||||||
|
self.focused_file = None
|
||||||
|
|
||||||
|
def navigate_back(self):
|
||||||
|
"""后退到上一个目录"""
|
||||||
|
if self.history_index > 0:
|
||||||
|
self.history_index -= 1
|
||||||
|
self.current_path = self.navigation_history[self.history_index]
|
||||||
|
self.selected_files.clear()
|
||||||
|
self.focused_file = None
|
||||||
|
|
||||||
|
def navigate_forward(self):
|
||||||
|
"""前进到下一个目录"""
|
||||||
|
if self.history_index < len(self.navigation_history) - 1:
|
||||||
|
self.history_index += 1
|
||||||
|
self.current_path = self.navigation_history[self.history_index]
|
||||||
|
self.selected_files.clear()
|
||||||
|
self.focused_file = None
|
||||||
|
|
||||||
|
def navigate_up(self):
|
||||||
|
"""导航到父目录"""
|
||||||
|
parent = self.current_path.parent
|
||||||
|
if parent != self.current_path: # 避免在根目录时循环
|
||||||
|
self.navigate_to(parent)
|
||||||
|
|
||||||
|
def navigate_to_selected(self):
|
||||||
|
"""导航到选中的目录"""
|
||||||
|
if self.focused_file and self.focused_file.is_dir():
|
||||||
|
self.navigate_to(self.focused_file)
|
||||||
|
|
||||||
|
def open_file(self, file_path: Path):
|
||||||
|
"""使用系统默认程序打开文件"""
|
||||||
|
try:
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
os.startfile(str(file_path))
|
||||||
|
elif platform.system() == "Darwin": # macOS
|
||||||
|
subprocess.run(["open", str(file_path)], check=True)
|
||||||
|
else: # Linux
|
||||||
|
subprocess.run(["xdg-open", str(file_path)], check=True)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"无法打开文件 {file_path}: {e}")
|
||||||
|
|
||||||
|
def get_file_size_string(self, path: Path) -> str:
|
||||||
|
"""获取文件大小的字符串表示"""
|
||||||
|
if path.is_dir():
|
||||||
|
return ""
|
||||||
|
|
||||||
|
try:
|
||||||
|
size = path.stat().st_size
|
||||||
|
for unit in ['B', 'KB', 'MB', 'GB']:
|
||||||
|
if size < 1024.0:
|
||||||
|
return f"{size:.1f} {unit}"
|
||||||
|
size /= 1024.0
|
||||||
|
return f"{size:.1f} TB"
|
||||||
|
except:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def should_show_file(self, path: Path) -> bool:
|
||||||
|
"""判断文件是否应该显示(基于搜索过滤)"""
|
||||||
|
if not self.search_filter:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return self.search_filter.lower() in path.name.lower()
|
||||||
|
|
||||||
|
def toggle_directory_expansion(self, path: Path):
|
||||||
|
"""切换目录展开/折叠状态"""
|
||||||
|
if path in self.expanded_directories:
|
||||||
|
self.expanded_directories.remove(path)
|
||||||
|
else:
|
||||||
|
self.expanded_directories.add(path)
|
||||||
|
|
||||||
|
def is_directory_expanded(self, path: Path) -> bool:
|
||||||
|
"""检查目录是否展开"""
|
||||||
|
return path in self.expanded_directories
|
||||||
|
|
||||||
|
def refresh_if_needed(self):
|
||||||
|
"""如果需要则刷新目录内容"""
|
||||||
|
if not self.auto_refresh_enabled:
|
||||||
|
return False
|
||||||
|
|
||||||
|
current_time = time.time()
|
||||||
|
if current_time - self.last_refresh_time > self.refresh_interval:
|
||||||
|
self.last_refresh_time = current_time
|
||||||
|
|
||||||
|
# 检查目录内容是否发生变化
|
||||||
|
if self._has_directory_changed():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _has_directory_changed(self) -> bool:
|
||||||
|
"""检查目录内容是否发生变化"""
|
||||||
|
try:
|
||||||
|
# 获取当前目录内容
|
||||||
|
dirs, files = self.get_directory_contents(self.current_path)
|
||||||
|
current_content = {
|
||||||
|
'dirs': {d.name: d.stat().st_mtime for d in dirs},
|
||||||
|
'files': {f.name: f.stat().st_mtime for f in files}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 获取缓存的内容
|
||||||
|
cache_key = str(self.current_path)
|
||||||
|
cached_content = self.last_directory_content.get(cache_key)
|
||||||
|
|
||||||
|
if cached_content is None:
|
||||||
|
# 首次访问,缓存内容
|
||||||
|
self.last_directory_content[cache_key] = current_content
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 比较内容
|
||||||
|
if cached_content != current_content:
|
||||||
|
# 内容发生变化,更新缓存
|
||||||
|
self.last_directory_content[cache_key] = current_content
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"检查目录变化时出错: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def force_refresh(self):
|
||||||
|
"""强制刷新当前目录"""
|
||||||
|
cache_key = str(self.current_path)
|
||||||
|
if cache_key in self.last_directory_content:
|
||||||
|
del self.last_directory_content[cache_key]
|
||||||
|
self.last_refresh_time = 0
|
||||||
|
|
||||||
|
def set_auto_refresh(self, enabled: bool):
|
||||||
|
"""设置自动刷新开关"""
|
||||||
|
self.auto_refresh_enabled = enabled
|
||||||
|
if enabled:
|
||||||
|
self.force_refresh()
|
||||||
|
|
||||||
|
def get_project_root(self) -> Path:
|
||||||
|
"""获取项目根目录"""
|
||||||
|
current = Path(__file__).resolve()
|
||||||
|
while current.parent != current:
|
||||||
|
if (current / "main.py").exists() or (current / ".git").exists():
|
||||||
|
return current
|
||||||
|
current = current.parent
|
||||||
|
return Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
def get_relative_path(self, path: Path) -> str:
|
||||||
|
"""获取相对于项目根目录的路径"""
|
||||||
|
try:
|
||||||
|
return str(path.relative_to(self.project_root))
|
||||||
|
except ValueError:
|
||||||
|
return str(path)
|
||||||
|
|
||||||
|
def start_drag(self, files: List[Path]):
|
||||||
|
"""开始拖拽操作"""
|
||||||
|
self.dragged_files = files.copy()
|
||||||
|
|
||||||
|
def clear_drag(self):
|
||||||
|
"""清除拖拽状态"""
|
||||||
|
self.dragged_files.clear()
|
||||||
|
|
||||||
|
def is_dragging(self) -> bool:
|
||||||
|
"""检查是否正在拖拽"""
|
||||||
|
return len(self.dragged_files) > 0
|
||||||
|
|
||||||
|
def get_dragged_files(self) -> List[Path]:
|
||||||
|
"""获取当前拖拽的文件列表"""
|
||||||
|
return self.dragged_files.copy()
|
||||||
|
|
||||||
|
def can_drag_to_scene(self) -> bool:
|
||||||
|
"""检查是否可以拖拽到场景中"""
|
||||||
|
if not self.is_dragging():
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 检查是否有支持的3D模型文件
|
||||||
|
supported_extensions = {'.gltf', '.glb', '.fbx', '.bam', '.egg', '.obj'}
|
||||||
|
for file_path in self.dragged_files:
|
||||||
|
if file_path.suffix.lower() in supported_extensions:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
352
demo.py
352
demo.py
@ -31,6 +31,7 @@ from project.project_manager import ProjectManager
|
|||||||
from core.InfoPanelManager import InfoPanelManager
|
from core.InfoPanelManager import InfoPanelManager
|
||||||
from core.collision_manager import CollisionManager
|
from core.collision_manager import CollisionManager
|
||||||
from core.CustomMouseController import CustomMouseController
|
from core.CustomMouseController import CustomMouseController
|
||||||
|
from core.resource_manager import ResourceManager
|
||||||
|
|
||||||
# 拖拽监控类
|
# 拖拽监控类
|
||||||
class DragDropMonitor:
|
class DragDropMonitor:
|
||||||
@ -150,6 +151,9 @@ class MyWorld(CoreWorld):
|
|||||||
# 初始化碰撞管理器
|
# 初始化碰撞管理器
|
||||||
self.collision_manager = CollisionManager(self)
|
self.collision_manager = CollisionManager(self)
|
||||||
|
|
||||||
|
# 初始化资源管理器
|
||||||
|
self.resource_manager = ResourceManager(self)
|
||||||
|
|
||||||
# 初始化自定义鼠标控制器(视角移动)
|
# 初始化自定义鼠标控制器(视角移动)
|
||||||
self.mouse_controller = CustomMouseController(self)
|
self.mouse_controller = CustomMouseController(self)
|
||||||
self.mouse_controller.setUp(mouse_speed=25, move_speed=20)
|
self.mouse_controller.setUp(mouse_speed=25, move_speed=20)
|
||||||
@ -262,6 +266,7 @@ class MyWorld(CoreWorld):
|
|||||||
self.showConsole = True
|
self.showConsole = True
|
||||||
self.showScriptPanel = True
|
self.showScriptPanel = True
|
||||||
self.showToolbar = True
|
self.showToolbar = True
|
||||||
|
self.showResourceManager = True
|
||||||
|
|
||||||
# 菜单状态管理
|
# 菜单状态管理
|
||||||
self.show_new_project_dialog = False
|
self.show_new_project_dialog = False
|
||||||
@ -542,6 +547,12 @@ class MyWorld(CoreWorld):
|
|||||||
|
|
||||||
# 绘制拖拽界面
|
# 绘制拖拽界面
|
||||||
self._draw_drag_drop_interface()
|
self._draw_drag_drop_interface()
|
||||||
|
|
||||||
|
# 检查鼠标释放事件(用于处理拖拽结束)
|
||||||
|
if imgui.is_mouse_released(0) and self.is_dragging:
|
||||||
|
if not imgui.is_any_window_hovered():
|
||||||
|
# 在3D视图中释放,处理模型导入
|
||||||
|
self._handle_drag_drop_completion()
|
||||||
|
|
||||||
def _draw_docked_layout(self, window_width, window_height):
|
def _draw_docked_layout(self, window_width, window_height):
|
||||||
"""绘制可停靠的布局(支持拖拽)"""
|
"""绘制可停靠的布局(支持拖拽)"""
|
||||||
@ -549,6 +560,10 @@ class MyWorld(CoreWorld):
|
|||||||
if self.showSceneTree:
|
if self.showSceneTree:
|
||||||
self._draw_scene_tree()
|
self._draw_scene_tree()
|
||||||
|
|
||||||
|
# 资源管理器面板
|
||||||
|
if self.showResourceManager:
|
||||||
|
self._draw_resource_manager()
|
||||||
|
|
||||||
# 属性面板
|
# 属性面板
|
||||||
if self.showPropertyPanel:
|
if self.showPropertyPanel:
|
||||||
self._draw_property_panel()
|
self._draw_property_panel()
|
||||||
@ -610,6 +625,7 @@ class MyWorld(CoreWorld):
|
|||||||
if view_menu:
|
if view_menu:
|
||||||
_, self.showToolbar = imgui.menu_item("工具栏", "", self.showToolbar, True)
|
_, self.showToolbar = imgui.menu_item("工具栏", "", self.showToolbar, True)
|
||||||
_, self.showSceneTree = imgui.menu_item("场景树", "", self.showSceneTree, True)
|
_, self.showSceneTree = imgui.menu_item("场景树", "", self.showSceneTree, True)
|
||||||
|
_, self.showResourceManager = imgui.menu_item("资源管理器", "", self.showResourceManager, True)
|
||||||
_, self.showPropertyPanel = imgui.menu_item("属性面板", "", self.showPropertyPanel, True)
|
_, self.showPropertyPanel = imgui.menu_item("属性面板", "", self.showPropertyPanel, True)
|
||||||
_, self.showConsole = imgui.menu_item("控制台", "", self.showConsole, True)
|
_, self.showConsole = imgui.menu_item("控制台", "", self.showConsole, True)
|
||||||
_, self.showScriptPanel = imgui.menu_item("脚本管理", "", self.showScriptPanel, True)
|
_, self.showScriptPanel = imgui.menu_item("脚本管理", "", self.showScriptPanel, True)
|
||||||
@ -739,6 +755,304 @@ class MyWorld(CoreWorld):
|
|||||||
imgui.text("(空)")
|
imgui.text("(空)")
|
||||||
imgui.tree_pop()
|
imgui.tree_pop()
|
||||||
|
|
||||||
|
def _draw_resource_manager(self):
|
||||||
|
"""绘制资源管理器面板"""
|
||||||
|
# 使用面板类型的窗口标志,支持docking
|
||||||
|
flags = self.style_manager.get_window_flags("panel")
|
||||||
|
|
||||||
|
with self.style_manager.begin_styled_window("资源管理器", self.showResourceManager, flags):
|
||||||
|
self.showResourceManager = True # 确保窗口保持打开
|
||||||
|
|
||||||
|
# 获取资源管理器实例
|
||||||
|
rm = self.resource_manager
|
||||||
|
|
||||||
|
# 工具栏
|
||||||
|
imgui.text("文件浏览器")
|
||||||
|
imgui.separator()
|
||||||
|
|
||||||
|
# 导航按钮
|
||||||
|
if imgui.button("◀"):
|
||||||
|
rm.navigate_back()
|
||||||
|
imgui.same_line()
|
||||||
|
if imgui.button("▶"):
|
||||||
|
rm.navigate_forward()
|
||||||
|
imgui.same_line()
|
||||||
|
if imgui.button("▲"):
|
||||||
|
rm.navigate_up()
|
||||||
|
imgui.same_line()
|
||||||
|
if imgui.button("🏠"):
|
||||||
|
rm.navigate_to(rm.project_root / "Resources")
|
||||||
|
imgui.same_line()
|
||||||
|
if imgui.button("🔄"):
|
||||||
|
rm.force_refresh()
|
||||||
|
|
||||||
|
# 自动刷新开关
|
||||||
|
imgui.same_line()
|
||||||
|
changed, rm.auto_refresh_enabled = imgui.checkbox("自动刷新", rm.auto_refresh_enabled)
|
||||||
|
if changed:
|
||||||
|
rm.set_auto_refresh(rm.auto_refresh_enabled)
|
||||||
|
|
||||||
|
imgui.same_line()
|
||||||
|
imgui.text(" ")
|
||||||
|
imgui.same_line()
|
||||||
|
|
||||||
|
# 路径输入框
|
||||||
|
changed, new_path = imgui.input_text("路径", str(rm.current_path), 256)
|
||||||
|
if changed:
|
||||||
|
try:
|
||||||
|
rm.navigate_to(Path(new_path))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 搜索框
|
||||||
|
changed, rm.search_filter = imgui.input_text("搜索", rm.search_filter, 256)
|
||||||
|
|
||||||
|
imgui.separator()
|
||||||
|
|
||||||
|
# 检查自动刷新
|
||||||
|
if rm.refresh_if_needed():
|
||||||
|
# 目录内容发生变化,可以在这里添加通知逻辑
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 获取目录内容
|
||||||
|
dirs, files = rm.get_directory_contents(rm.current_path)
|
||||||
|
|
||||||
|
# 显示目录
|
||||||
|
for dir_path in dirs:
|
||||||
|
if not rm.should_show_file(dir_path):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 目录图标和名称
|
||||||
|
icon = rm.get_file_icon(dir_path.name, is_folder=True)
|
||||||
|
node_open = False
|
||||||
|
|
||||||
|
# 检查是否被选中
|
||||||
|
is_selected = dir_path in rm.selected_files
|
||||||
|
|
||||||
|
# 使用TreeNode来显示目录
|
||||||
|
if is_selected:
|
||||||
|
imgui.push_style_color(imgui.Col_.header, imgui.get_style().colors[imgui.Col_.header_hovered])
|
||||||
|
|
||||||
|
node_open = imgui.tree_node(f"{icon} {dir_path.name}")
|
||||||
|
|
||||||
|
if is_selected:
|
||||||
|
imgui.pop_style_color()
|
||||||
|
|
||||||
|
# 处理选择
|
||||||
|
if imgui.is_item_clicked():
|
||||||
|
if imgui.get_io().key_ctrl:
|
||||||
|
# 多选模式
|
||||||
|
if is_selected:
|
||||||
|
rm.selected_files.discard(dir_path)
|
||||||
|
else:
|
||||||
|
rm.selected_files.add(dir_path)
|
||||||
|
else:
|
||||||
|
# 单选模式
|
||||||
|
rm.selected_files.clear()
|
||||||
|
rm.selected_files.add(dir_path)
|
||||||
|
rm.focused_file = dir_path
|
||||||
|
|
||||||
|
# 双击导航
|
||||||
|
if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0):
|
||||||
|
rm.navigate_to(dir_path)
|
||||||
|
|
||||||
|
# 右键菜单
|
||||||
|
if imgui.is_item_hovered() and imgui.is_mouse_clicked(1):
|
||||||
|
rm.show_context_menu = True
|
||||||
|
rm.context_menu_file = dir_path
|
||||||
|
rm.context_menu_position = (imgui.get_mouse_pos().x, imgui.get_mouse_pos().y)
|
||||||
|
|
||||||
|
# 如果节点展开,显示子内容
|
||||||
|
if node_open:
|
||||||
|
# 获取子目录内容
|
||||||
|
subdirs, subfiles = rm.get_directory_contents(dir_path)
|
||||||
|
|
||||||
|
# 显示子目录
|
||||||
|
for subdir in subdirs:
|
||||||
|
if not rm.should_show_file(subdir):
|
||||||
|
continue
|
||||||
|
|
||||||
|
subicon = rm.get_file_icon(subdir.name, is_folder=True)
|
||||||
|
sub_is_selected = subdir in rm.selected_files
|
||||||
|
|
||||||
|
if sub_is_selected:
|
||||||
|
imgui.push_style_color(imgui.Col_.header, imgui.get_style().colors[imgui.Col_.header_hovered])
|
||||||
|
|
||||||
|
sub_node_open = imgui.tree_node(f" {subicon} {subdir.name}")
|
||||||
|
|
||||||
|
if sub_is_selected:
|
||||||
|
imgui.pop_style_color()
|
||||||
|
|
||||||
|
# 处理子目录的选择
|
||||||
|
if imgui.is_item_clicked():
|
||||||
|
if imgui.get_io().key_ctrl:
|
||||||
|
if sub_is_selected:
|
||||||
|
rm.selected_files.discard(subdir)
|
||||||
|
else:
|
||||||
|
rm.selected_files.add(subdir)
|
||||||
|
else:
|
||||||
|
rm.selected_files.clear()
|
||||||
|
rm.selected_files.add(subdir)
|
||||||
|
rm.focused_file = subdir
|
||||||
|
|
||||||
|
# 双击子目录导航
|
||||||
|
if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0):
|
||||||
|
rm.navigate_to(subdir)
|
||||||
|
|
||||||
|
# 右键菜单
|
||||||
|
if imgui.is_item_hovered() and imgui.is_mouse_clicked(1):
|
||||||
|
rm.show_context_menu = True
|
||||||
|
rm.context_menu_file = subdir
|
||||||
|
rm.context_menu_position = (imgui.get_mouse_pos().x, imgui.get_mouse_pos().y)
|
||||||
|
|
||||||
|
if sub_node_open:
|
||||||
|
imgui.tree_pop()
|
||||||
|
|
||||||
|
# 显示子文件
|
||||||
|
for subfile in subfiles:
|
||||||
|
if not rm.should_show_file(subfile):
|
||||||
|
continue
|
||||||
|
|
||||||
|
subicon = rm.get_file_icon(subfile.name)
|
||||||
|
sub_is_selected = subfile in rm.selected_files
|
||||||
|
|
||||||
|
selected = imgui.selectable(f" {subicon} {subfile.name}", sub_is_selected)
|
||||||
|
|
||||||
|
# 处理子文件的选择
|
||||||
|
if selected:
|
||||||
|
if imgui.get_io().key_ctrl:
|
||||||
|
if sub_is_selected:
|
||||||
|
rm.selected_files.discard(subfile)
|
||||||
|
else:
|
||||||
|
rm.selected_files.add(subfile)
|
||||||
|
else:
|
||||||
|
rm.selected_files.clear()
|
||||||
|
rm.selected_files.add(subfile)
|
||||||
|
rm.focused_file = subfile
|
||||||
|
|
||||||
|
# 双击子文件操作
|
||||||
|
if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0):
|
||||||
|
if subfile.suffix.lower() in ['.gltf', '.glb', '.fbx', '.bam', '.egg', '.obj']:
|
||||||
|
self.scene_manager.importModel(str(subfile))
|
||||||
|
self.add_info_message(f"正在导入模型: {subfile.name}")
|
||||||
|
else:
|
||||||
|
rm.open_file(subfile)
|
||||||
|
|
||||||
|
# 右键菜单
|
||||||
|
if imgui.is_item_hovered() and imgui.is_mouse_clicked(1):
|
||||||
|
rm.show_context_menu = True
|
||||||
|
rm.context_menu_file = subfile
|
||||||
|
rm.context_menu_position = (imgui.get_mouse_pos().x, imgui.get_mouse_pos().y)
|
||||||
|
|
||||||
|
imgui.tree_pop()
|
||||||
|
|
||||||
|
# 显示文件
|
||||||
|
for file_path in files:
|
||||||
|
if not rm.should_show_file(file_path):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 文件图标和名称
|
||||||
|
icon = rm.get_file_icon(file_path.name)
|
||||||
|
file_size = rm.get_file_size_string(file_path)
|
||||||
|
|
||||||
|
# 检查是否被选中
|
||||||
|
is_selected = file_path in rm.selected_files
|
||||||
|
|
||||||
|
# 使用Selectable来显示文件
|
||||||
|
selected = imgui.selectable(f"{icon} {file_path.name}", is_selected)
|
||||||
|
|
||||||
|
# 显示文件大小
|
||||||
|
if file_size:
|
||||||
|
imgui.same_line()
|
||||||
|
imgui.text_disabled(file_size)
|
||||||
|
|
||||||
|
# 处理选择
|
||||||
|
if selected:
|
||||||
|
if imgui.get_io().key_ctrl:
|
||||||
|
# 多选模式
|
||||||
|
if is_selected:
|
||||||
|
rm.selected_files.discard(file_path)
|
||||||
|
else:
|
||||||
|
rm.selected_files.add(file_path)
|
||||||
|
else:
|
||||||
|
# 单选模式
|
||||||
|
rm.selected_files.clear()
|
||||||
|
rm.selected_files.add(file_path)
|
||||||
|
rm.focused_file = file_path
|
||||||
|
|
||||||
|
# 处理拖拽开始
|
||||||
|
if imgui.is_item_active() and imgui.is_item_hovered():
|
||||||
|
if imgui.is_mouse_dragging(0):
|
||||||
|
# 开始拖拽
|
||||||
|
drag_files = list(rm.selected_files) if rm.selected_files else [file_path]
|
||||||
|
rm.start_drag(drag_files)
|
||||||
|
self.is_dragging = True
|
||||||
|
self.show_drag_overlay = True
|
||||||
|
|
||||||
|
# 双击打开文件
|
||||||
|
if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0):
|
||||||
|
# 检查是否是支持的3D模型格式
|
||||||
|
if file_path.suffix.lower() in ['.gltf', '.glb', '.fbx', '.bam', '.egg', '.obj']:
|
||||||
|
# 导入3D模型
|
||||||
|
self.add_info_message(f"正在导入模型: {file_path.name}")
|
||||||
|
self.scene_manager.importModel(str(file_path))
|
||||||
|
else:
|
||||||
|
# 使用系统默认程序打开
|
||||||
|
rm.open_file(file_path)
|
||||||
|
|
||||||
|
# 右键菜单
|
||||||
|
if imgui.is_item_hovered() and imgui.is_mouse_clicked(1):
|
||||||
|
rm.show_context_menu = True
|
||||||
|
rm.context_menu_file = file_path
|
||||||
|
rm.context_menu_position = (imgui.get_mouse_pos().x, imgui.get_mouse_pos().y)
|
||||||
|
|
||||||
|
# 右键菜单
|
||||||
|
if rm.show_context_menu and rm.context_menu_file:
|
||||||
|
imgui.set_next_window_position(rm.context_menu_position[0], rm.context_menu_position[1])
|
||||||
|
with imgui_ctx.begin_popup("context_menu", imgui.WindowFlags_.no_title_bar |
|
||||||
|
imgui.WindowFlags_.no_resize | imgui.WindowFlags_.always_auto_resize) as popup:
|
||||||
|
if popup:
|
||||||
|
if rm.context_menu_file.is_dir():
|
||||||
|
if imgui.menu_item("打开"):
|
||||||
|
rm.navigate_to(rm.context_menu_file)
|
||||||
|
imgui.separator()
|
||||||
|
if imgui.menu_item("重命名"):
|
||||||
|
print(f"重命名文件夹: {rm.context_menu_file.name}")
|
||||||
|
if imgui.menu_item("删除"):
|
||||||
|
print(f"删除文件夹: {rm.context_menu_file.name}")
|
||||||
|
else:
|
||||||
|
if imgui.menu_item("打开"):
|
||||||
|
rm.open_file(rm.context_menu_file)
|
||||||
|
imgui.separator()
|
||||||
|
if imgui.menu_item("导入到场景"):
|
||||||
|
if rm.context_menu_file.suffix.lower() in ['.gltf', '.glb', '.fbx', '.bam', '.egg', '.obj']:
|
||||||
|
self.add_info_message(f"正在导入模型: {rm.context_menu_file.name}")
|
||||||
|
self.scene_manager.importModel(str(rm.context_menu_file))
|
||||||
|
if imgui.menu_item("重命名"):
|
||||||
|
print(f"重命名文件: {rm.context_menu_file.name}")
|
||||||
|
if imgui.menu_item("删除"):
|
||||||
|
print(f"删除文件: {rm.context_menu_file.name}")
|
||||||
|
|
||||||
|
imgui.separator()
|
||||||
|
if imgui.menu_item("复制路径"):
|
||||||
|
imgui.set_clipboard_text(str(rm.context_menu_file))
|
||||||
|
self.add_info_message("路径已复制到剪贴板")
|
||||||
|
if imgui.menu_item("在文件管理器中显示"):
|
||||||
|
import platform
|
||||||
|
import subprocess
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
subprocess.run(["explorer", "/select,", str(rm.context_menu_file)])
|
||||||
|
elif platform.system() == "Darwin":
|
||||||
|
subprocess.run(["open", "-R", str(rm.context_menu_file)])
|
||||||
|
else:
|
||||||
|
subprocess.run(["xdg-open", str(rm.context_menu_file.parent)])
|
||||||
|
|
||||||
|
# 如果点击其他地方,关闭菜单
|
||||||
|
if imgui.is_mouse_clicked(0) or imgui.is_mouse_clicked(1):
|
||||||
|
if not imgui.is_window_hovered():
|
||||||
|
rm.show_context_menu = False
|
||||||
|
rm.context_menu_file = None
|
||||||
|
|
||||||
def _draw_property_panel(self):
|
def _draw_property_panel(self):
|
||||||
"""绘制属性面板"""
|
"""绘制属性面板"""
|
||||||
# 使用面板类型的窗口标志,支持docking
|
# 使用面板类型的窗口标志,支持docking
|
||||||
@ -1984,6 +2298,12 @@ class MyWorld(CoreWorld):
|
|||||||
|
|
||||||
def _draw_drag_drop_interface(self):
|
def _draw_drag_drop_interface(self):
|
||||||
"""绘制拖拽界面"""
|
"""绘制拖拽界面"""
|
||||||
|
# 检查资源管理器的拖拽状态
|
||||||
|
if self.resource_manager.is_dragging():
|
||||||
|
self.is_dragging = True
|
||||||
|
self.dragged_files = self.resource_manager.get_dragged_files()
|
||||||
|
self.show_drag_overlay = True
|
||||||
|
|
||||||
# 绘制拖拽覆盖层
|
# 绘制拖拽覆盖层
|
||||||
if self.show_drag_overlay:
|
if self.show_drag_overlay:
|
||||||
self._draw_drag_overlay()
|
self._draw_drag_overlay()
|
||||||
@ -1992,6 +2312,38 @@ class MyWorld(CoreWorld):
|
|||||||
if self.is_dragging and self.dragged_files:
|
if self.is_dragging and self.dragged_files:
|
||||||
# 显示拖拽状态
|
# 显示拖拽状态
|
||||||
self._draw_drag_status()
|
self._draw_drag_status()
|
||||||
|
|
||||||
|
# 检查是否释放鼠标(结束拖拽)
|
||||||
|
if imgui.is_mouse_released(0):
|
||||||
|
self._handle_drag_drop_completion()
|
||||||
|
|
||||||
|
def _handle_drag_drop_completion(self):
|
||||||
|
"""处理拖拽完成"""
|
||||||
|
# 检查是否在3D视图中释放
|
||||||
|
mouse_pos = imgui.get_mouse_pos()
|
||||||
|
viewport = imgui.get_main_viewport()
|
||||||
|
|
||||||
|
# 简单检查:如果不在任何ImGui窗口上,则认为是在3D视图中
|
||||||
|
if not imgui.is_any_window_hovered():
|
||||||
|
# 导入支持的3D模型文件
|
||||||
|
imported_count = 0
|
||||||
|
for file_path in self.dragged_files:
|
||||||
|
if file_path.suffix.lower() in ['.gltf', '.glb', '.fbx', '.bam', '.egg', '.obj']:
|
||||||
|
try:
|
||||||
|
self.scene_manager.importModel(str(file_path))
|
||||||
|
self.add_success_message(f"成功导入模型: {file_path.name}")
|
||||||
|
imported_count += 1
|
||||||
|
except Exception as e:
|
||||||
|
self.add_error_message(f"导入模型失败 {file_path.name}: {e}")
|
||||||
|
|
||||||
|
if imported_count > 0:
|
||||||
|
self.add_success_message(f"共导入 {imported_count} 个模型")
|
||||||
|
|
||||||
|
# 清除拖拽状态
|
||||||
|
self.is_dragging = False
|
||||||
|
self.dragged_files.clear()
|
||||||
|
self.show_drag_overlay = False
|
||||||
|
self.resource_manager.clear_drag()
|
||||||
|
|
||||||
def _draw_drag_overlay(self):
|
def _draw_drag_overlay(self):
|
||||||
"""绘制拖拽覆盖层"""
|
"""绘制拖拽覆盖层"""
|
||||||
|
|||||||
24
imgui.ini
24
imgui.ini
@ -1,5 +1,5 @@
|
|||||||
[Window][Debug##Default]
|
[Window][Debug##Default]
|
||||||
Pos=60,60
|
Pos=28,731
|
||||||
Size=400,400
|
Size=400,400
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ DockId=0x00000007,0
|
|||||||
|
|
||||||
[Window][场景树]
|
[Window][场景树]
|
||||||
Pos=0,20
|
Pos=0,20
|
||||||
Size=285,883
|
Size=285,753
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000001,0
|
DockId=0x00000001,0
|
||||||
|
|
||||||
@ -42,10 +42,10 @@ Collapsed=0
|
|||||||
DockId=0x00000005,0
|
DockId=0x00000005,0
|
||||||
|
|
||||||
[Window][控制台]
|
[Window][控制台]
|
||||||
Pos=0,905
|
Pos=880,775
|
||||||
Size=1524,111
|
Size=644,241
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x0000000A,0
|
DockId=0x0000000C,0
|
||||||
|
|
||||||
[Window][脚本管理]
|
[Window][脚本管理]
|
||||||
Pos=1526,520
|
Pos=1526,520
|
||||||
@ -98,15 +98,23 @@ Pos=625,258
|
|||||||
Size=600,500
|
Size=600,500
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
|
[Window][资源管理器]
|
||||||
|
Pos=0,775
|
||||||
|
Size=878,241
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x0000000B,0
|
||||||
|
|
||||||
[Docking][Data]
|
[Docking][Data]
|
||||||
DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,20 Size=1850,996 Split=X
|
DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,20 Size=1850,996 Split=X
|
||||||
DockNode ID=0x00000003 Parent=0x08BD597D SizeRef=1524,996 Split=Y
|
DockNode ID=0x00000003 Parent=0x08BD597D SizeRef=1524,996 Split=Y
|
||||||
DockNode ID=0x00000009 Parent=0x00000003 SizeRef=1380,883 Split=X
|
DockNode ID=0x00000009 Parent=0x00000003 SizeRef=1380,753 Split=X
|
||||||
DockNode ID=0x00000001 Parent=0x00000009 SizeRef=285,730 HiddenTabBar=1 Selected=0xE0015051
|
DockNode ID=0x00000001 Parent=0x00000009 SizeRef=285,730 HiddenTabBar=1 Selected=0xE0015051
|
||||||
DockNode ID=0x00000002 Parent=0x00000009 SizeRef=1237,730 Split=Y
|
DockNode ID=0x00000002 Parent=0x00000009 SizeRef=1237,730 Split=Y
|
||||||
DockNode ID=0x00000007 Parent=0x00000002 SizeRef=1380,32 HiddenTabBar=1 Selected=0x43A39006
|
DockNode ID=0x00000007 Parent=0x00000002 SizeRef=1380,32 HiddenTabBar=1 Selected=0x43A39006
|
||||||
DockNode ID=0x00000008 Parent=0x00000002 SizeRef=1380,849 CentralNode=1 Selected=0x5E5F7166
|
DockNode ID=0x00000008 Parent=0x00000002 SizeRef=1380,719 CentralNode=1 Selected=0x5E5F7166
|
||||||
DockNode ID=0x0000000A Parent=0x00000003 SizeRef=1380,111 HiddenTabBar=1 Selected=0x5428E753
|
DockNode ID=0x0000000A Parent=0x00000003 SizeRef=1380,241 Split=X Selected=0x5428E753
|
||||||
|
DockNode ID=0x0000000B Parent=0x0000000A SizeRef=878,111 HiddenTabBar=1 Selected=0x3A2E05C3
|
||||||
|
DockNode ID=0x0000000C Parent=0x0000000A SizeRef=644,111 HiddenTabBar=1 Selected=0x5428E753
|
||||||
DockNode ID=0x00000004 Parent=0x08BD597D SizeRef=324,996 Split=Y Selected=0x5DB6FF37
|
DockNode ID=0x00000004 Parent=0x08BD597D SizeRef=324,996 Split=Y Selected=0x5DB6FF37
|
||||||
DockNode ID=0x00000005 Parent=0x00000004 SizeRef=304,498 HiddenTabBar=1 Selected=0x5DB6FF37
|
DockNode ID=0x00000005 Parent=0x00000004 SizeRef=304,498 HiddenTabBar=1 Selected=0x5DB6FF37
|
||||||
DockNode ID=0x00000006 Parent=0x00000004 SizeRef=304,496 HiddenTabBar=1 Selected=0x3188AB8D
|
DockNode ID=0x00000006 Parent=0x00000004 SizeRef=304,496 HiddenTabBar=1 Selected=0x3188AB8D
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user