CmWindowDockManager.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. #include "CmWindowDockManager.h"
  2. #include "CmEditorWindowManager.h"
  3. #include "CmQtEditorWindow.h"
  4. #include "CmDebug.h"
  5. #include <QtWidgets/QWidget>
  6. #include <QtWidgets/QLayout>
  7. #include <QtWidgets/QSplitter>
  8. namespace CamelotEditor
  9. {
  10. WindowDockManager::WindowDockManager(QWidget* centralWidget, QtDockOverlayWidget* dockOverlayWidget)
  11. :mDockOverlayWidget(dockOverlayWidget), mCentralWidget(centralWidget), mCentralSplitter(nullptr), mLastDraggedWindow(nullptr)
  12. {
  13. QVBoxLayout* boxLayout = new QVBoxLayout();
  14. boxLayout->setMargin(0);
  15. boxLayout->setSpacing(0);
  16. mCentralWidget->setLayout(boxLayout);
  17. mCentralSplitter = new QSplitter(mCentralWidget);
  18. mCentralSplitter->setChildrenCollapsible(false);
  19. boxLayout->addWidget(mCentralSplitter);
  20. }
  21. void WindowDockManager::windowDragged(QtEditorWindow* window, const QPoint& mousePos)
  22. {
  23. assert(window != nullptr);
  24. if(mLastDraggedWindow != window)
  25. {
  26. mLastDragPosition = mousePos;
  27. mLastDraggedWindow = window;
  28. }
  29. if(!window->isDocked())
  30. {
  31. vector<UINT32>::type windowsToIgnore;
  32. windowsToIgnore.push_back(window->getId()); // Ignore myself
  33. QtEditorWindow* windowUnderMouse = gEditorWindowManager().getWindowAtPosition(mousePos, windowsToIgnore);
  34. QWidget* dragOverWidget = nullptr;
  35. bool contentUnderMouse = false;
  36. bool tabBarUnderMouse = false;
  37. if(windowUnderMouse != nullptr)
  38. {
  39. dragOverWidget = windowUnderMouse->getContentWidget();
  40. QPoint localMousePos = dragOverWidget->mapFromGlobal(mousePos);
  41. if(dragOverWidget->geometry().contains(localMousePos))
  42. contentUnderMouse = true;
  43. QWidget* tabBarWidget = windowUnderMouse->getTabWidget();
  44. localMousePos = tabBarWidget->mapFromGlobal(mousePos);
  45. if(tabBarWidget->geometry().contains(localMousePos))
  46. tabBarUnderMouse = true;
  47. }
  48. if(!tabBarUnderMouse && dragOverWidget == nullptr && isPositionInDockArea(mousePos))
  49. {
  50. dragOverWidget = mCentralWidget;
  51. contentUnderMouse = true;
  52. }
  53. if(contentUnderMouse)
  54. {
  55. mDockOverlayWidget->highlightTabDropLocation(-1);
  56. mDockOverlayWidget->disableTabDropOverlay();
  57. // Draw dock overlay
  58. if(dragOverWidget != nullptr)
  59. {
  60. WindowDragDropLocation dragLocation = getDropLocationAtPosition(dragOverWidget, mousePos);
  61. std::vector<QPolygon> dropLocations = getDropLocations(dragOverWidget);
  62. QPoint drawOffset = mCentralWidget->mapToGlobal(QPoint(0, 0)) - mDockOverlayWidget->mapToGlobal(QPoint(0, 0));
  63. mDockOverlayWidget->enableDropOverlay(dropLocations, drawOffset);
  64. if(dragLocation != CM_WINDROP_CENTER)
  65. mDockOverlayWidget->highlightDropLocation(dragLocation);
  66. else
  67. mDockOverlayWidget->highlightDropLocation(CM_WINDROP_NONE);
  68. }
  69. else
  70. {
  71. mDockOverlayWidget->highlightDropLocation(CM_WINDROP_NONE);
  72. mDockOverlayWidget->disableDropOverlay();
  73. }
  74. }
  75. if(tabBarUnderMouse)
  76. {
  77. mDockOverlayWidget->highlightDropLocation(CM_WINDROP_NONE);
  78. mDockOverlayWidget->disableDropOverlay();
  79. // Draw tab bar overlay
  80. if(windowUnderMouse != nullptr)
  81. {
  82. std::vector<QPolygon> tabDropLocations = windowUnderMouse->getTabBarDropLocations();
  83. INT32 activeTabDropLocation = windowUnderMouse->getActiveTabBarDropLocation(mousePos);
  84. if(activeTabDropLocation != -1)
  85. {
  86. QPoint drawOffset = -mDockOverlayWidget->mapToGlobal(QPoint(0, 0));
  87. mDockOverlayWidget->enableTabDropOverlay(tabDropLocations, drawOffset);
  88. mDockOverlayWidget->highlightTabDropLocation(activeTabDropLocation);
  89. }
  90. }
  91. }
  92. }
  93. else
  94. {
  95. QPoint diff = mLastDragPosition - mousePos;
  96. if(diff.manhattanLength() > 4)
  97. {
  98. undockWindow(window);
  99. }
  100. }
  101. }
  102. void WindowDockManager::windowReleased(QtEditorWindow* window, const QPoint& mousePos)
  103. {
  104. mDockOverlayWidget->highlightDropLocation(CM_WINDROP_NONE);
  105. mDockOverlayWidget->disableDropOverlay();
  106. mDockOverlayWidget->highlightTabDropLocation(-1);
  107. mDockOverlayWidget->disableTabDropOverlay();
  108. if(mLastDraggedWindow != window)
  109. {
  110. mLastDragPosition = mousePos;
  111. mLastDraggedWindow = window;
  112. }
  113. QPoint diff = mLastDragPosition - mousePos;
  114. bool wasDragged = diff.manhattanLength() > 4; // Ensure user actually moved the window
  115. if(wasDragged && !window->isDocked())
  116. {
  117. vector<UINT32>::type windowsToIgnore;
  118. windowsToIgnore.push_back(window->getId()); // Ignore myself
  119. QtEditorWindow* windowUnderCursor = gEditorWindowManager().getWindowAtPosition(mousePos, windowsToIgnore);
  120. if(windowUnderCursor != nullptr)
  121. {
  122. bool contentUnderMouse = false;
  123. bool tabBarUnderMouse = false;
  124. QWidget* contentWidget = windowUnderCursor->getContentWidget();
  125. QPoint localMousePos = contentWidget->mapFromGlobal(mousePos);
  126. if(contentWidget->geometry().contains(localMousePos))
  127. contentUnderMouse = true;
  128. QWidget* tabBarWidget = windowUnderCursor->getTabWidget();
  129. localMousePos = tabBarWidget->mapFromGlobal(mousePos);
  130. if(tabBarWidget->geometry().contains(localMousePos))
  131. tabBarUnderMouse = true;
  132. if(contentUnderMouse)
  133. {
  134. WindowDragDropLocation dropLocation = getDropLocationAtPosition(windowUnderCursor->getContentWidget(), mousePos);
  135. dockWindow(window, windowUnderCursor, dropLocation);
  136. }
  137. else if(tabBarUnderMouse)
  138. {
  139. INT32 dropLocation = windowUnderCursor->getActiveTabBarDropLocation(mousePos);
  140. if(dropLocation != -1)
  141. {
  142. while(window->getNumWidgets() > 0)
  143. {
  144. QtEditorWidget* widget = window->getWidget(0);
  145. window->removeWidget(0);
  146. windowUnderCursor->insertWidget(dropLocation, widget);
  147. }
  148. window->closeWindow();
  149. }
  150. }
  151. }
  152. else
  153. {
  154. if(isPositionInDockArea(mousePos))
  155. {
  156. WindowDragDropLocation dropLocation = getDropLocationAtPosition(mCentralWidget, mousePos);
  157. dockWindow(window, nullptr, dropLocation);
  158. }
  159. }
  160. }
  161. }
  162. void WindowDockManager::windowClosed(QtEditorWindow* window)
  163. {
  164. if(window->isDocked())
  165. undockWindow(window);
  166. }
  167. bool WindowDockManager::isDocked(const QtEditorWindow* window) const
  168. {
  169. auto findIter = mDockedWindows.find(const_cast<QtEditorWindow*>(window));
  170. return findIter != mDockedWindows.end();
  171. }
  172. WindowDragDropLocation WindowDockManager::getDockLocation(const QtEditorWindow* window) const
  173. {
  174. auto findIter = mDockedWindows.find(const_cast<QtEditorWindow*>(window));
  175. assert(findIter != mDockedWindows.end());
  176. return findIter->second.dockLocation;
  177. }
  178. INT32 WindowDockManager::getDockParentId(const QtEditorWindow* window) const
  179. {
  180. auto findIter = mDockedWindows.find(const_cast<QtEditorWindow*>(window));
  181. assert(findIter != mDockedWindows.end());
  182. return findIter->second.parentId;
  183. }
  184. bool WindowDockManager::isPositionInDockArea(const QPoint& globalPos)
  185. {
  186. QPoint globalWidgetPos = mCentralWidget->mapToGlobal(QPoint(0, 0));
  187. QRect widgetRect(globalWidgetPos, mCentralWidget->geometry().size());
  188. return widgetRect.contains(globalPos);
  189. }
  190. WindowDragDropLocation WindowDockManager::getDropLocationAtPosition(const QWidget* widget, const QPoint& globalPos)
  191. {
  192. assert(widget != nullptr);
  193. QPoint localPos = mCentralWidget->mapFromGlobal(globalPos);
  194. std::vector<QPolygon> dragLocations = getDropLocations(widget);
  195. int idx = 0;
  196. for(auto iter = dragLocations.begin(); iter != dragLocations.end(); ++iter)
  197. {
  198. if(iter->containsPoint(localPos, Qt::OddEvenFill))
  199. return (WindowDragDropLocation)idx;
  200. ++idx;
  201. }
  202. return CM_WINDROP_NONE;
  203. }
  204. void WindowDockManager::dockWindow(QtEditorWindow* windowToDock, QtEditorWindow* dockAtWidget, WindowDragDropLocation dockAtPosition)
  205. {
  206. assert(windowToDock != nullptr);
  207. auto findIter = mDockedWindows.find(windowToDock);
  208. assert(findIter == mDockedWindows.end());
  209. if(dockAtPosition == CM_WINDROP_NONE || dockAtPosition == CM_WINDROP_CENTER)
  210. return;
  211. if(dockAtWidget == nullptr)
  212. {
  213. mCentralSplitter->addWidget(windowToDock);
  214. windowToDock->dock();
  215. }
  216. else
  217. {
  218. QSplitter* parentSplitter = dynamic_cast<QSplitter*>(dockAtWidget->parentWidget());
  219. if(parentSplitter == nullptr)
  220. {
  221. LOGWRN("Trying to dock a window to a widget that doesn't have a parent splitter.");
  222. return;
  223. }
  224. if(parentSplitter->orientation() == Qt::Horizontal)
  225. {
  226. int idxDockAt = parentSplitter->indexOf(dockAtWidget);
  227. if(dockAtPosition == CM_WINDROP_LEFT)
  228. parentSplitter->insertWidget(idxDockAt, windowToDock);
  229. else if(dockAtPosition == CM_WINDROP_RIGHT)
  230. parentSplitter->insertWidget(idxDockAt + 1, windowToDock);
  231. else // Top or bottom
  232. {
  233. QSplitter* newSplitter = new QSplitter();
  234. newSplitter->setOrientation(Qt::Vertical);
  235. newSplitter->setChildrenCollapsible(false);
  236. if(dockAtPosition == CM_WINDROP_TOP)
  237. {
  238. newSplitter->addWidget(windowToDock);
  239. newSplitter->addWidget(dockAtWidget);
  240. }
  241. else
  242. {
  243. newSplitter->addWidget(dockAtWidget);
  244. newSplitter->addWidget(windowToDock);
  245. }
  246. parentSplitter->insertWidget(idxDockAt, newSplitter);
  247. }
  248. }
  249. else
  250. {
  251. int idxDockAt = parentSplitter->indexOf(dockAtWidget);
  252. if(dockAtPosition == CM_WINDROP_TOP)
  253. parentSplitter->insertWidget(idxDockAt, windowToDock);
  254. else if(dockAtPosition == CM_WINDROP_BOTTOM)
  255. parentSplitter->insertWidget(idxDockAt + 1, windowToDock);
  256. else // Left or right
  257. {
  258. QSplitter* newSplitter = new QSplitter();
  259. newSplitter->setOrientation(Qt::Horizontal);
  260. newSplitter->setChildrenCollapsible(false);
  261. if(dockAtPosition == CM_WINDROP_LEFT)
  262. {
  263. newSplitter->addWidget(windowToDock);
  264. newSplitter->addWidget(dockAtWidget);
  265. }
  266. else
  267. {
  268. newSplitter->addWidget(dockAtWidget);
  269. newSplitter->addWidget(windowToDock);
  270. }
  271. parentSplitter->insertWidget(idxDockAt, newSplitter);
  272. }
  273. }
  274. windowToDock->dock();
  275. }
  276. DockedWindowInfo dockedInfo;
  277. dockedInfo.dockLocation = dockAtPosition;
  278. if(dockAtWidget == nullptr)
  279. dockedInfo.parentId = -1;
  280. else
  281. dockedInfo.parentId = dockAtWidget->getId();
  282. mDockedWindows[windowToDock] = dockedInfo;
  283. }
  284. void WindowDockManager::undockWindow(QtEditorWindow* windowToUndock)
  285. {
  286. CM_ASSERT(windowToUndock != nullptr);
  287. QSplitter* parentSplitter = dynamic_cast<QSplitter*>(windowToUndock->parentWidget());
  288. if(parentSplitter == nullptr)
  289. {
  290. LOGWRN("Trying to dock a window to a widget that doesn't have a parent splitter.");
  291. return;
  292. }
  293. windowToUndock->setParent(mCentralWidget);
  294. windowToUndock->undock();
  295. windowToUndock->show();
  296. // Check if there is just one widget in splitter, so there's no need for a splitter at all
  297. if(parentSplitter != mCentralSplitter && parentSplitter->count() == 1)
  298. {
  299. QSplitter* topParentSplitter = dynamic_cast<QSplitter*>(parentSplitter->parentWidget());
  300. if(topParentSplitter == nullptr)
  301. {
  302. CM_EXCEPT(InternalErrorException, "Splitter is not root splitter, but doesn't have a splitter parent.");
  303. }
  304. QWidget* remainingWidget = parentSplitter->widget(0);
  305. int splitterIdx = topParentSplitter->indexOf(parentSplitter);
  306. topParentSplitter->insertWidget(splitterIdx, remainingWidget);
  307. delete parentSplitter;
  308. }
  309. auto findIter = mDockedWindows.find(windowToUndock);
  310. CM_ASSERT(findIter != mDockedWindows.end());
  311. mDockedWindows.erase(findIter);
  312. }
  313. std::vector<QPolygon> WindowDockManager::getDropLocations(const QWidget* widget)
  314. {
  315. QPoint topLeft = widget->mapToGlobal(QPoint(0, 0));
  316. topLeft = mCentralWidget->mapFromGlobal(topLeft);
  317. QRect largeRect(topLeft.x() + 1, topLeft.y() + 1, widget->width() - 2, widget->height() - 2);
  318. int sizeOffset = widget->width() < widget->height() ? (widget->width() / 10) : (widget->height() / 10);
  319. QRect smallRect(topLeft.x() + sizeOffset, topLeft.y() + sizeOffset, widget->width() - 2 * sizeOffset, widget->height() - 2 * sizeOffset);
  320. QPolygon left(4);
  321. left[0] = largeRect.topLeft();
  322. left[1] = largeRect.bottomLeft();
  323. left[2] = smallRect.bottomLeft();
  324. left[3] = smallRect.topLeft();
  325. QPolygon right(4);
  326. right[0] = largeRect.topRight();
  327. right[1] = largeRect.bottomRight();
  328. right[2] = smallRect.bottomRight();
  329. right[3] = smallRect.topRight();
  330. QPolygon top(4);
  331. top[0] = largeRect.topLeft();
  332. top[1] = largeRect.topRight();
  333. top[2] = smallRect.topRight();
  334. top[3] = smallRect.topLeft();
  335. QPolygon bottom(4);
  336. bottom[0] = largeRect.bottomRight();
  337. bottom[1] = largeRect.bottomLeft();
  338. bottom[2] = smallRect.bottomLeft();
  339. bottom[3] = smallRect.bottomRight();
  340. QPolygon center(4);
  341. center[0] = smallRect.topLeft();
  342. center[1] = smallRect.topRight();
  343. center[2] = smallRect.bottomRight();
  344. center[3] = smallRect.bottomLeft();
  345. std::vector<QPolygon> dragLocations;
  346. dragLocations.push_back(left);
  347. dragLocations.push_back(right);
  348. dragLocations.push_back(top);
  349. dragLocations.push_back(bottom);
  350. dragLocations.push_back(center);
  351. return dragLocations;
  352. }
  353. WindowDockManager& gWindowDockManager()
  354. {
  355. return WindowDockManager::instance();
  356. }
  357. }