diff --git a/.idea/SimpleIDE.iml b/.idea/SimpleIDE.iml new file mode 100644 index 0000000..6cb8b9a --- /dev/null +++ b/.idea/SimpleIDE.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..de071b2 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..bc24bff --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..5ecedbf --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1703843144486 + + + + \ No newline at end of file diff --git a/DockPanel.py b/DockPanel.py new file mode 100644 index 0000000..46f440a --- /dev/null +++ b/DockPanel.py @@ -0,0 +1,120 @@ +from PyQt5.QtWidgets import QWidget +from PyQt5.QtWidgets import QApplication +from PyQt5.QtWidgets import QLabel +from PyQt5.QtWidgets import QVBoxLayout +from PyQt5.QtWidgets import QHBoxLayout +from PyQt5.QtWidgets import QFrame +from PyQt5.QtWidgets import QPushButton +from PyQt5.QtCore import Qt, pyqtSignal +from PyQt5.QtCore import QSize, QPoint, QMimeData +from PyQt5.QtGui import QDrag +from Manager import DragManager + + +class DragHeader(QFrame): + close_request = pyqtSignal(QWidget) + retrieve_request = pyqtSignal(QWidget) + adjust_request = pyqtSignal(QWidget, QPoint) + + def __init__(self, title: str, parent: 'DockPanel'): + super().__init__(parent) + + self.__place_cube = parent + self.__pressed_mark = (False, QPoint) + + self.layout_inst = QHBoxLayout(self) + self.layout_inst.setSpacing(0) + self.layout_inst.setContentsMargins(0,0,0,0) + + self.show_label=QLabel(title, self) + self.show_label.setContentsMargins(2, 0, 0, 0) + self.layout_inst.addWidget(self.show_label, stretch=1, alignment=Qt.AlignLeft) + + self.min_button=QPushButton("-") + self.min_button.clicked.connect(lambda : self.retrieve_request[QWidget].emit(parent)) + self.min_button.setMaximumSize(22,22) + self.min_button.setMaximumSize(22,22) + self.layout_inst.addWidget(self.min_button,stretch=0, alignment=Qt.AlignRight) + self.close_button=QPushButton("x") + self.close_button.clicked.connect(lambda : self.close_request[QWidget].emit(parent)) + self.close_button.setMaximumSize(22,22) + self.close_button.setMaximumSize(22,22) + self.layout_inst.addWidget(self.close_button, stretch=0, alignment=Qt.AlignRight) + pass + + def mousePressEvent(self, a0): + super(DragHeader, self).mousePressEvent(a0) + self.__pressed_mark = (True, self.__place_cube.mapToParent(a0.pos())) + pass + + def mouseReleaseEvent(self, a0): + super(DragHeader, self).mouseReleaseEvent(a0) + self.__pressed_mark = (False, QPoint()) + pass + + def mouseMoveEvent(self, a0): + super(DragHeader, self).mouseMoveEvent(a0) + if self.__pressed_mark[0]: + self.adjust_request[QWidget,QPoint].emit(self, self.__place_cube.mapToParent(a0.pos())) + + +class DockPanel(QWidget): + + def __init__(self, title:str, show_inst:QWidget|None, pinst:QWidget): + super(DockPanel, self).__init__(pinst) + + self.setMinimumSize(60, 40) + self.setWindowTitle(title) + self.parent_res = pinst + + self.layout_inst = QVBoxLayout(self) + self.layout_inst.setSpacing(0) + self.layout_inst.setContentsMargins(0,2,0,0) + + self.drag_header = DragHeader(title, self) + self.layout_inst.addWidget(self.drag_header) + self.drag_header.setMinimumHeight(26) + self.drag_header.setMaximumHeight(26) + self.drag_header.setFrameShape(QFrame.Shape.Panel) + self.drag_header.setFrameShadow(QFrame.Shadow.Raised) + self.drag_header.setLineWidth(3) + self.drag_header.adjust_request[QWidget,QPoint].connect(self.__adjust_accept) + + if show_inst is not None: + self.layout_inst.addWidget(show_inst) + else: + self.layout_inst.addWidget(QWidget()) + + self.default_header = True + self.can_replace = True + self.can_retrieve = True + self.can_close = True + + DragManager.instance().register_dockpanel(self) + pass + + def reset_parent_res(self, pinst): + self.parent_res = pinst + + def __adjust_accept(self, view: QWidget, pt: QPoint): + drag_trans = QDrag(self) + mine_data = QMimeData() + mine_data.setText("view-drags(" + str(self.__hash__()) + ")") + drag_trans.setMimeData(mine_data) + drag_trans.setPixmap(self.grab(self.rect())) + drag_trans.setHotSpot(QPoint(5,5)) + drag_trans.exec() + + def __del__(self): + DragManager.instance().remove_dockpanel(self) + + def sync_status(self): + self.drag_header.setVisible(self.default_header) + + +if __name__ == "__main__": + app=QApplication([]) + app.installEventFilter(DragManager()) + win=DockPanel("empty-window",None,None) + win.show() + app.exec() \ No newline at end of file diff --git a/Manager.py b/Manager.py new file mode 100644 index 0000000..562b52b --- /dev/null +++ b/Manager.py @@ -0,0 +1,127 @@ +from PyQt5.QtWidgets import QWidget, QApplication +from PyQt5.QtCore import QObject, QEvent, QRect, QMargins, Qt +from PyQt5.QtGui import QPainter, QColor +from enum import Enum + + +class AcceptPanel(QWidget): + + def __init__(self, parent): + super(AcceptPanel, self).__init__(parent) + self.left_rect = QRect() + self.right_rect = QRect() + self.top_rect = QRect() + self.bottom_rect = QRect() + self.center_rect = QRect() + self.hover_rect = QRect() + self.setMouseTracking(True) + self.setAcceptDrops(True) + + def resizeEvent(self, a0): + total_rect = self.rect() + total_rect = total_rect - QMargins(5, 5, 5, 5) + anchor_width = 30 + + self.left_rect = QRect(int(total_rect.left()), int(total_rect.center().y() - anchor_width / 2), int(anchor_width), int(anchor_width)) + self.right_rect = QRect(int(total_rect.right() - anchor_width), int(total_rect.center().y() - anchor_width / 2), int(anchor_width), int(anchor_width)) + self.top_rect = QRect(int(total_rect.center().x() - anchor_width / 2), int(total_rect.top()), int(anchor_width), int(anchor_width)) + self.bottom_rect = QRect(int(total_rect.center().x() - anchor_width / 2), int(total_rect.bottom() - anchor_width), int(anchor_width), int(anchor_width)) + self.center_rect = QRect(int(total_rect.center().x() - anchor_width / 2), int(total_rect.center().y() - anchor_width / 2), int(anchor_width), int(anchor_width)) + + def async_with(self, target_view: QWidget): + self.resize(target_view.size()) + + def paintEvent(self, a0): + super(AcceptPanel, self).paintEvent(a0) + painter = QPainter(self) + painter.fillRect(self.rect(), Qt.lightGray) + + painter.fillRect(self.hover_rect, Qt.gray) + + painter.fillRect(self.top_rect, Qt.green) + painter.fillRect(self.bottom_rect, Qt.green) + painter.fillRect(self.left_rect, Qt.green) + painter.fillRect(self.right_rect, Qt.green) + painter.fillRect(self.center_rect, Qt.green) + + def mouseMoveEvent(self, a0): + self.hover_rect = self.rect() + print(a0.pos()) + + if self.left_rect.contains(a0.pos()): + self.hover_rect.setWidth(int(self.hover_rect.width() / 3)) + elif self.right_rect.contains(a0.pos()): + self.hover_rect.setWidth(int(self.hover_rect.width() / 3)) + self.hover_rect.moveLeft(int(self.rect().right() - self.hover_rect.width())) + elif self.top_rect.contains(a0.pos()): + self.hover_rect.setHeight(int(self.hover_rect.height() / 3)) + elif self.center_rect.contains(a0.pos()): + pass + elif self.bottom_rect.contains(a0.pos()): + self.hover_rect.setHeight(int(self.hover_rect.height() / 3)) + self.hover_rect.moveTop(int(self.rect().bottom() - self.hover_rect.height())) + else: + self.hover_rect = QRect() + + self.update() + + +class DragManager(QObject): + __unique_inst: 'DragManager' = None + + def __init__(self): + super(DragManager, self).__init__() + self.__dock_map = {} + + @classmethod + def instance(cls): + if cls.__unique_inst is None: + cls.__unique_inst = DragManager() + return cls.__unique_inst + + def __peak_window(self, obj: QWidget): + if obj is None: + return None + + if obj.__class__.__name__ == "QMainWindow": + return obj + + return self.__peak_window(obj.parent()) + + def eventFilter(self, sender: QObject | None, event: QEvent | None): + if event.type() == QEvent.Type.DragMove: + if sender.isWindowType(): + sender.requestActivate() + + for ip in self.__dock_map: + view_inst: QWidget = self.get_dockpanel(ip) + if view_inst is None: + return False + + gpos = sender.mapToGlobal(event.pos()) + point = view_inst.mapFromGlobal(gpos) + if (view_inst.rect().contains(point)) and (self.__peak_window(view_inst).isActiveWindow()): + print(view_inst.rect()) + + pass + + return super(DragManager, self).eventFilter(sender, event) + + def register_dockpanel(self, view): + self.__dock_map[str(view.__hash__())] = view + + def remove_dockpanel(self, view): + if str(view.__hash__()) in self.__dock_map: + self.__dock_map.pop(str(view.__hash__())) + + def get_dockpanel(self, address: str): + if address in self.__dock_map: + return self.__dock_map[address] + return None + + +if __name__ == "__main__": + app = QApplication([]) + accept = AcceptPanel(None) + accept.show() + app.exec() diff --git a/SplitPanel.py b/SplitPanel.py new file mode 100644 index 0000000..58ceb87 --- /dev/null +++ b/SplitPanel.py @@ -0,0 +1,122 @@ +import Manager +from DockPanel import DockPanel +from PyQt5.QtWidgets import QApplication, QWidget, QFrame +from enum import Enum +from PyQt5.QtCore import pyqtSignal, QPoint +from PyQt5.QtCore import QRect, Qt +from PyQt5.QtWidgets import QMainWindow + + +class SplitType(Enum): + SPLIT_H = 0 + SPLIT_V = 1 + + +class DragSplitter(QFrame): + adjustSignal = pyqtSignal(QPoint) + + def __init__(self, split:SplitType, parent:QWidget): + super(DragSplitter, self).__init__(parent) + + self.setFrameShape(QFrame.Shape.WinPanel) + self.setFrameShadow(QFrame.Shadow.Raised) + + if split == SplitType.SPLIT_H: + self.setCursor(Qt.CursorShape.SplitHCursor) + else: + self.setCursor(Qt.CursorShape.SplitVCursor) + + pass + + def mouseMoveEvent(self, a0): + super().mouseMoveEvent(a0) + up_point = self.mapToParent(a0.pos()) + self.adjustSignal.emit(up_point) + + +class SplitPanel(QWidget): + def __init__(self, a: DockPanel, b: DockPanel, split: SplitType, parent:QWidget = None): + super(SplitPanel, self).__init__(parent) + + self.splitter_widget = DragSplitter(split, self) + self.splitter_widget.adjustSignal[QPoint].connect(self.__splitter_adjust) + + self.split_member = (a, b) + self.split_member[0].setParent(self) + self.split_member[1].setParent(self) + + self.split_info = (split, 0.5, 7) + self.sync_status() + pass + + def __view_list(self) -> [DockPanel]: + retval: [DockPanel] = [self.split_member[0], self.split_member[1]] + return retval + + def set_split_info(self, o: SplitType, pos: float, width: float = 8): + self.split_info = (o, pos, width) + self.sync_status() + + def sync_status(self): + if self.split_info[0] == SplitType.SPLIT_H: + total_width = self.width() + width_a = total_width * self.split_info[1] + width_b = total_width - width_a - self.split_info[2] + + self.split_member[0].setGeometry(0, 0, int(width_a), self.height()) + self.split_member[1].setGeometry(int(width_a + self.split_info[2]), 0, int(width_b), self.height()) + + handle_rect = QRect(int(width_a), 0, int(self.split_info[2]), self.height()) + self.splitter_widget.setGeometry(handle_rect) + else: + total_height = self.height() + height_a = total_height * self.split_info[1] + height_b = total_height - height_a - self.split_info[2] + + self.split_member[0].setGeometry(0, 0, self.width(), int(height_a)) + self.split_member[1].setGeometry(0, int(height_a + self.split_info[2]) - 1, self.width(), int(height_b) + 1) + + handle_rect = QRect(0, int(height_a), self.width(), int(self.split_info[2])) + self.splitter_widget.setGeometry(handle_rect) + pass + + def resizeEvent(self, a0): + super().resizeEvent(a0) + self.sync_status() + + def __splitter_adjust(self, pos: QPoint): + if self.split_info[0] == SplitType.SPLIT_H: + leftw = self.split_member[0].minimumWidth() + rightw = self.split_member[1].minimumWidth() + if (pos.x() >= leftw) and (pos.x() <= self.width() - rightw): + self.split_info = (self.split_info[0], pos.x() / self.width(), self.split_info[2]) + else: + toph = self.split_member[0].minimumHeight() + bottomh = self.split_member[1].minimumHeight() + if (pos.y() >= toph) and (pos.y() <= self.height() - bottomh): + self.split_info = (self.split_info[0], pos.y() / self.height(), self.split_info[2]) + self.sync_status() + + def replace_view(self, new: DockPanel, old: DockPanel): + if old in self.__view_list() and new not in self.__view_list(): + if self.split_member[0] == old: + self.split_member = (new, self.split_member[1], self.split_member[2]) + else: + self.split_member = (self.split_member[0], new, self.split_member[2]) + old.setParent(None) + self.sync_status() + pass + + +if __name__ == "__main__": + app = QApplication([]) + ow = QMainWindow() + app.installEventFilter(Manager.DragManager.instance()) + + a = DockPanel("docka", None, None) + b = DockPanel("dockb", None, None) + win = SplitPanel(a, b, SplitType.SPLIT_H) + ow.setCentralWidget(win) + + ow.show() + app.exec() \ No newline at end of file diff --git a/__pycache__/DockPanel.cpython-312.pyc b/__pycache__/DockPanel.cpython-312.pyc new file mode 100644 index 0000000..82689ea Binary files /dev/null and b/__pycache__/DockPanel.cpython-312.pyc differ diff --git a/__pycache__/Manager.cpython-312.pyc b/__pycache__/Manager.cpython-312.pyc new file mode 100644 index 0000000..9b8cf98 Binary files /dev/null and b/__pycache__/Manager.cpython-312.pyc differ diff --git a/test.py b/test.py new file mode 100644 index 0000000..26fb453 --- /dev/null +++ b/test.py @@ -0,0 +1,2 @@ +dict = {} +print(dict.__class__.__name__) \ No newline at end of file