From 351b446a1514fae2ecd9a75ca37659604a46164f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=89=E5=AE=87=E6=B8=85=E9=9F=B3?= <2422523675@qq.com> Date: Sat, 30 Dec 2023 16:15:07 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/SimpleIDE.iml | 10 ++ .idea/codeStyles/codeStyleConfig.xml | 5 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 7 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + .idea/workspace.xml | 169 ++++++++++++++++++ DockPanel.py | 120 +++++++++++++ Manager.py | 127 +++++++++++++ SplitPanel.py | 122 +++++++++++++ __pycache__/DockPanel.cpython-312.pyc | Bin 0 -> 8419 bytes __pycache__/Manager.cpython-312.pyc | Bin 0 -> 3658 bytes test.py | 2 + 13 files changed, 582 insertions(+) create mode 100644 .idea/SimpleIDE.iml create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/workspace.xml create mode 100644 DockPanel.py create mode 100644 Manager.py create mode 100644 SplitPanel.py create mode 100644 __pycache__/DockPanel.cpython-312.pyc create mode 100644 __pycache__/Manager.cpython-312.pyc create mode 100644 test.py 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 0000000000000000000000000000000000000000..82689eaf4ef13a785c619c4db7b60be23f4b0385 GIT binary patch literal 8419 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 literal 0 HcmV?d00001 diff --git a/__pycache__/Manager.cpython-312.pyc b/__pycache__/Manager.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9b8cf98af6e032569acb3fa15fba7a97f57b55da GIT binary patch literal 3658 zcmb6cU2hZF_0D+gOfq)HNxVsbWO1@dUdebMHO(oO93j_1}HI76LHw*A4ZDO@#at7Y?{9lieS|WQ!<7VG<ZLM7iPqHb}l;k6PvN_V6Y>BimgeB*R!mSd;t#aEgn`cB=@<>hp#)ZV1 z-kJ9^nS>UL8d^GKHqHD*Q|46z5KS}Fm*Q&7Fg-J87uA#@ai%a6PA}-HwKT<8mkYtUXrs`W38y1DdHM!YCxd{unfjxD=vt3VQ}{Yh;d5 zT&sM`uYKy`wgY^_%73|p@XVEsVdLv$?WtBvHiU4D) z;#EbU^8uZIjr^R6w9OK!)f7;Ta5NR2SLu|wPp(@3xU{trhkWnf10+q?jAWAc2+3c*p zhmww6k(1F3_=VHe#GKhI%dtdM*JW8pdBK%$4o#lEc!8$TW%|VnOUC7N>f)@H%p}wg zC(mB2C^(W?qHS<~IZ&hKh$FQbFnSjZU$2m7yzlyzn`3!idcqHv_V=uDpNqSIB6W5^ z*_ItHz+?;bi{p_7SP_k?){Yy&64g_CQ_T`8!V!t3KKNMnkr>kzOXxa!B1m6@(d5xJ z(Ns*W*T~sLQhfl174kx8Eebt(p=U=JcorPK>sy<6;_WSo?L{$|7lS)|kai+RSNc*s zR`nt5aPYHq%)$h8WLB6Z828dNUP}k35ftXiVYok=WoFSnP6F6SYXn{pz>VNoYP%A5 z5UlJJreIV{*@-1_Gyqf>HK}M)g{WzW-i#W(B44$V(DY=Y6tkG69Y90(!(c~F#gIN( zt~tKEuRcC%P)M$0*sqY1(7F~Xb##GgH{Mx)rzrI0g}&S;kA=5O9i3|*y%0N!VqaeD z%e`^$$ekm1j~2wy9e&g{vqx7`ib_qlip3>X4G>_ACbA6P59Y+=%Chm=2Tov4H-N(_zOMlR%dE$t)|u;< zL6ZY}oZ*}^@!oG^9qvHCW*l_z-+dE@A49JHfg{bt2s(5|zcYi9`UAKhlV!Insb;0c zIjO_sj3ua}=xvkx7^Ws~g)p=u)2r!r##s9zRiUQq#``ftTZ}^cYDXF$bUhi%Y$lpg z6Q%&g_T%*VMEX)RVY)JD-SolEIhsyZ_Be{Yk&mTQ1|+0TUq>dbnr?AbZE>bML$#Da z{eZIzykJH9IV}NVCEiZOKE$GiCaY!4=0#1tYBd~_n@73sOd5G$rt3TmK^s=A`bXb_ zo#n3DWUZ0VgES==pq`fm|HkR{)1RIB;^ghntx)m6X#T+HV_~!;zE%|b^J4!KaiHYy zEc%D?{-G!S!!J7bZ>4XfUlQJTbdxO&O2xrb`N30<*ur3Fv$@pKv(qu~j|0IMUA@IF zDc>dCAN-T_K-wNIbe-JfO5J_M?$Lbr=tHj1ePYvH3iNJG-Iyu_28w}W`M|OJ>eIm3 zqp@NroDYTnrWHazEr#avq4}pF?O9;oP3iW?t&zh1BToavVCE1BzCnnDTgQZGCa@hgn z(hZ8G_i|LfEXxn*nO`@U8IgrwiRjAM?k)L-oG^CA>(gB@+n7w+||0~lm zFOU(ru`8tW`gD_y; zw{I$#$$4QATJ_fWjq%*6e8=#Pceo^WSSm^F6ic|-BFo9NvXH=+gjbe7U5F;^cZKx= zGeudBrc!Cc`lkR7PwOFR3f40;slt0xp{N7JD6+~S#l*5IvQ?%inxCS9Y!9OjCOkVV z1~D~}kY$On>l}vedv<}t7Hic`%xrxQhOd83O8rAC;muvH$) zx?onuS-GXj{bsIfm%yjo-sYZQa)<5@+&Nk%IDfeKNcrohZy&Xmmb!Avdp{O zf!xG@34AQeSc0b{uPI(wnlVm{%oz6HBb}nNElo|YgPKTF^_9w|;Ow7Y&?vs|C{}NZ zPY#Oz`z+oMh%E