Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2021 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 :
23 : #include "dropcontroller.h"
24 :
25 : #include "../idockwindow.h"
26 :
27 : #include "qml/Muse/Dock/dockcentralview.h"
28 : #include "qml/Muse/Dock/dockingholderview.h"
29 : #include "qml/Muse/Dock/dockpageview.h"
30 : #include "qml/Muse/Dock/dockpanelview.h"
31 : #include "qml/Muse/Dock/docktoolbarview.h"
32 :
33 : #include "globaltypes.h"
34 :
35 : // This include pulls in kdbindings/signal.h which is incompatible with
36 : // Qt's `emit` macro being defined. Temporarily undefine it, then restore it.
37 : #ifdef emit
38 : #undef emit
39 : #include "kddockwidgets/src/core/DragController_p.h"
40 : #include "kddockwidgets/src/core/DropArea.h"
41 : #define emit
42 : #else
43 : #include "kddockwidgets/src/core/DragController_p.h"
44 : #include "kddockwidgets/src/core/DropArea.h"
45 : #endif
46 :
47 : #include "kddockwidgets/src/core/DockWidget.h"
48 : #include "kddockwidgets/src/qtquick/views/DockWidget.h"
49 : #include "kddockwidgets/src/qtquick/views/View.h"
50 :
51 : #include "log.h"
52 :
53 : using KDDropLocation = KDDockWidgets::DropLocation;
54 :
55 : namespace muse::dock {
56 : static constexpr double MAX_DISTANCE_TO_HOLDER = 50;
57 :
58 0 : static KDDropLocation dropLocationToKDDockLocation(Location location)
59 : {
60 0 : switch (location) {
61 0 : case Location::Undefined: return KDDropLocation::DropLocation_None;
62 0 : case Location::Left: return KDDropLocation::DropLocation_Left;
63 0 : case Location::Right: return KDDropLocation::DropLocation_Right;
64 0 : case Location::Center: return KDDropLocation::DropLocation_Center;
65 0 : case Location::Top: return KDDropLocation::DropLocation_Top;
66 0 : case Location::Bottom: return KDDropLocation::DropLocation_Bottom;
67 : }
68 :
69 0 : return KDDropLocation::DropLocation_None;
70 : }
71 :
72 0 : static bool isPointAllowedForDrop(const QPoint& point, const DropDestination& dropDestination)
73 : {
74 0 : QRect dropRect = dropDestination.dock->frameGeometry();
75 :
76 0 : if (!dropRect.contains(point)) {
77 0 : return false;
78 : }
79 :
80 0 : if (dropDestination.dropDistance == 0) {
81 0 : return true;
82 : }
83 :
84 0 : if (dropDestination.dropLocation == Location::Left) {
85 0 : if (std::abs(dropRect.left() - point.x()) <= dropDestination.dropDistance) {
86 0 : return true;
87 : }
88 : }
89 :
90 0 : if (dropDestination.dropLocation == Location::Right) {
91 0 : if (std::abs(dropRect.right() - point.x()) <= dropDestination.dropDistance) {
92 0 : return true;
93 : }
94 : }
95 :
96 0 : return false;
97 : }
98 : }
99 :
100 : using namespace muse::dock;
101 :
102 0 : DropController::DropController(KDDockWidgets::Core::ClassicDropIndicatorOverlay* classicIndicators,
103 : KDDockWidgets::Core::View* parent,
104 0 : const modularity::ContextPtr& iocCtx)
105 : : Contextable(iocCtx)
106 0 : , m_classicIndicators(classicIndicators)
107 0 : , m_dropArea(parent ? parent->asDropAreaController() : nullptr)
108 : {
109 0 : }
110 :
111 0 : KDDropLocation DropController::hover(KDDockWidgets::Point globalPos)
112 : {
113 : //! NOTE: The drop destinations are resolved in the main window only, so don't allow
114 : //! dropping into the layout of a floating window, e.g. of an undocked toolbar under the cursor
115 0 : if (m_dropArea && !m_dropArea->isInMainWindow()) {
116 0 : return KDDockWidgets::DropLocation_None;
117 : }
118 :
119 0 : DockBase* draggedDock = this->draggedDock();
120 0 : if (!draggedDock) {
121 0 : return KDDockWidgets::DropLocation_None;
122 : }
123 :
124 0 : QPoint hoveredLocalPos = dockWindow()->asItem().mapFromGlobal(globalPos).toPoint();
125 0 : DropDestination dropDestination = resolveDropDestination(draggedDock, hoveredLocalPos);
126 :
127 0 : if (auto toolBar = dynamic_cast<DockToolBarView*>(draggedDock)) {
128 0 : updateToolBarOrientation(toolBar, dropDestination);
129 : }
130 :
131 0 : setCurrentDropDestination(draggedDock, dropDestination);
132 :
133 0 : if (m_currentDropDestination.isValid()) {
134 0 : auto* dw = m_currentDropDestination.dock->dockWidget();
135 0 : auto* dwView = qobject_cast<KDDockWidgets::QtQuick::DockWidget*>(
136 0 : KDDockWidgets::QtQuick::asQQuickItem(dw));
137 0 : if (dwView) {
138 0 : m_classicIndicators->setHoveredGroup(dwView->group());
139 : }
140 : }
141 :
142 0 : return dropLocationToKDDockLocation(m_currentDropDestination.dropLocation);
143 : }
144 :
145 0 : void DropController::setVisible(bool visible)
146 : {
147 0 : if (visible) {
148 0 : return;
149 : }
150 :
151 0 : DockBase* draggedDock = this->draggedDock();
152 0 : if (auto toolBar = dynamic_cast<DockToolBarView*>(draggedDock)) {
153 0 : updateToolBarOrientation(toolBar);
154 : }
155 :
156 0 : endHover();
157 : }
158 :
159 0 : KDDockWidgets::Point DropController::posForIndicator(KDDockWidgets::DropLocation) const
160 : {
161 0 : return KDDockWidgets::Point();
162 : }
163 :
164 0 : void DropController::endHover()
165 : {
166 0 : if (!m_currentDropDestination.isValid()) {
167 0 : return;
168 : }
169 :
170 0 : m_currentDropDestination.dock->hideHighlighting();
171 :
172 0 : if (m_currentDropDestination.dock->type() == DockType::DockingHolder) {
173 0 : m_currentDropDestination.dock->close();
174 : }
175 :
176 0 : m_currentDropDestination.clear();
177 : }
178 :
179 0 : bool DropController::isMouseOverDock(const QPoint& mouseLocalPos, const DockBase* dock) const
180 : {
181 0 : QRect geometry = dock ? dock->frameGeometry() : QRect();
182 0 : return geometry.contains(mouseLocalPos);
183 : }
184 :
185 0 : void DropController::updateToolBarOrientation(DockToolBarView* draggedToolBar, const DropDestination& dropDestination)
186 : {
187 0 : IF_ASSERT_FAILED(draggedToolBar) {
188 0 : return;
189 : }
190 :
191 0 : muse::Orientation orientation = muse::Orientation::Horizontal;
192 :
193 0 : if (!dropDestination.isValid()) {
194 0 : draggedToolBar->setOrientation(static_cast<Qt::Orientation>(orientation));
195 0 : return;
196 : }
197 :
198 0 : switch (dropDestination.dock->location()) {
199 0 : case Location::Left:
200 : case Location::Right:
201 0 : orientation = muse::Orientation::Vertical;
202 0 : break;
203 0 : case Location::Top:
204 : case Location::Bottom:
205 0 : orientation = muse::Orientation::Horizontal;
206 0 : break;
207 0 : case Location::Center:
208 : case Location::Undefined:
209 0 : break;
210 : }
211 :
212 0 : draggedToolBar->setOrientation(static_cast<Qt::Orientation>(orientation));
213 : }
214 :
215 0 : void DropController::setCurrentDropDestination(const DockBase* draggedDock, const DropDestination& dropDestination)
216 : {
217 0 : if (m_currentDropDestination == dropDestination) {
218 0 : return;
219 : }
220 :
221 0 : endHover();
222 :
223 0 : m_currentDropDestination = dropDestination;
224 :
225 0 : if (!m_currentDropDestination.isValid()) {
226 0 : return;
227 : }
228 :
229 0 : auto showHighlighting = [this, draggedDock]() {
230 0 : QRect highlightingRect = resolveHighlightingRect(draggedDock, m_currentDropDestination);
231 0 : m_currentDropDestination.dock->showHighlighting(highlightingRect);
232 0 : };
233 :
234 0 : if (m_currentDropDestination.dock->type() != DockType::DockingHolder) {
235 0 : showHighlighting();
236 0 : return;
237 : }
238 :
239 0 : switch (m_currentDropDestination.dock->location()) {
240 0 : case Location::Left:
241 : case Location::Right:
242 0 : m_currentDropDestination.dock->setMinimumWidth(draggedDock->minimumWidth());
243 0 : break;
244 0 : case Location::Top:
245 : case Location::Bottom:
246 0 : m_currentDropDestination.dock->setMinimumHeight(draggedDock->minimumHeight());
247 0 : break;
248 0 : case Location::Center:
249 : case Location::Undefined:
250 0 : break;
251 : }
252 :
253 0 : m_currentDropDestination.dock->open();
254 0 : showHighlighting();
255 0 : m_currentDropDestination.dock->init();
256 : }
257 :
258 0 : DropDestination DropController::resolveDropDestination(const DockBase* draggedDock, const QPoint& localPos) const
259 : {
260 0 : if (draggedDock->type() == DockType::Panel) {
261 0 : DropDestination destination;
262 :
263 0 : destination.dock = resolvePanelForDrop(dynamic_cast<const DockPanelView*>(draggedDock), localPos);
264 0 : destination.dropLocation = resolveDropLocation(destination.dock, localPos);
265 :
266 0 : if (destination.isValid()) {
267 0 : return destination;
268 : }
269 : }
270 :
271 0 : const DockingHolderView* holder = resolveDockingHolder(draggedDock->type(), localPos);
272 0 : QList<DropDestination> destinations = draggedDock->dropDestinations();
273 :
274 0 : for (const DropDestination& destination : destinations) {
275 0 : if (holder == destination.dock) {
276 0 : return destination;
277 : }
278 :
279 0 : if (isPointAllowedForDrop(localPos, destination)) {
280 0 : return destination;
281 : }
282 : }
283 :
284 0 : return DropDestination();
285 0 : }
286 :
287 0 : DockingHolderView* DropController::resolveDockingHolder(DockType draggedDockType, const QPoint& localPos) const
288 : {
289 0 : if (!dockWindow()->asItem().contains(localPos)) {
290 0 : return nullptr;
291 : }
292 :
293 0 : QRect centralGeometry = currentPage()->centralDock()->frameGeometry();
294 :
295 : // TODO: Need to take any panels docked at top into account
296 0 : if (localPos.y() <= centralGeometry.top() + MAX_DISTANCE_TO_HOLDER) {
297 0 : return currentPage()->holder(draggedDockType, Location::Top);
298 : }
299 :
300 0 : if (localPos.y() >= centralGeometry.bottom() - MAX_DISTANCE_TO_HOLDER) {
301 0 : return currentPage()->holder(draggedDockType, Location::Bottom);
302 : }
303 :
304 0 : if (localPos.x() <= MAX_DISTANCE_TO_HOLDER) {
305 0 : return currentPage()->holder(draggedDockType, Location::Left);
306 : }
307 :
308 0 : if (localPos.x() >= dockWindow()->asItem().boundingRect().right() - MAX_DISTANCE_TO_HOLDER) {
309 0 : return currentPage()->holder(draggedDockType, Location::Right);
310 : }
311 :
312 0 : return nullptr;
313 : }
314 :
315 0 : DockPanelView* DropController::resolvePanelForDrop(const DockPanelView* panel, const QPoint& localPos) const
316 : {
317 0 : QList<DockPanelView*> panels = currentPage()->findPanelsForDropping(panel);
318 :
319 0 : for (DockPanelView* p : panels) {
320 0 : if (isMouseOverDock(localPos, p)) {
321 0 : return p;
322 : }
323 : }
324 :
325 0 : return nullptr;
326 0 : }
327 :
328 0 : muse::dock::Location DropController::resolveDropLocation(const DockBase* hoveredDock, const QPoint& localPos) const
329 : {
330 0 : if (!hoveredDock) {
331 0 : return Location::Undefined;
332 : }
333 :
334 0 : QRect geometry = hoveredDock->frameGeometry();
335 0 : Location dockLocation = hoveredDock->location();
336 :
337 0 : qreal frameEnd = geometry.bottom();
338 0 : qreal mousePos = localPos.y();
339 0 : Location beginDropLocation = Location::Top;
340 0 : Location endDropLocation = Location::Bottom;
341 :
342 0 : if (dockLocation == Location::Top || dockLocation == Location::Bottom) {
343 0 : mousePos = localPos.x();
344 0 : frameEnd = geometry.right();
345 0 : beginDropLocation = Location::Left;
346 0 : endDropLocation = Location::Right;
347 : }
348 :
349 0 : if (mousePos <= frameEnd / 3) {
350 0 : return beginDropLocation;
351 : }
352 :
353 0 : if (mousePos <= frameEnd / 1.5) {
354 0 : return Location::Center;
355 : }
356 :
357 0 : if (mousePos <= frameEnd) {
358 0 : return endDropLocation;
359 : }
360 :
361 0 : return Location::Undefined;
362 : }
363 :
364 0 : QRect DropController::resolveHighlightingRect(const DockBase* draggedDock, const DropDestination& destination) const
365 : {
366 0 : if (!destination.isValid()) {
367 0 : return QRect();
368 : }
369 :
370 0 : QRect frameGeometry = destination.dock->frameGeometry();
371 0 : int frameWidth = frameGeometry.width();
372 0 : int frameHeight = frameGeometry.height();
373 0 : QRect fullFrameHighlightingRect = QRect(0, 0, frameWidth, frameHeight);
374 :
375 0 : if (destination.dock->type() == DockType::DockingHolder) {
376 0 : return fullFrameHighlightingRect;
377 : }
378 :
379 0 : if (destination.dock->type() == DockType::Central) {
380 0 : int draggedDockWidth = draggedDock->frameGeometry().width();
381 :
382 0 : if (destination.dropLocation == Location::Left) {
383 0 : return QRect(0, 0, draggedDockWidth, frameHeight);
384 : }
385 :
386 0 : if (destination.dropLocation == Location::Right) {
387 0 : return QRect(frameWidth - draggedDockWidth, 0, draggedDockWidth, frameHeight);
388 : }
389 : }
390 :
391 0 : switch (destination.dropLocation) {
392 0 : case Location::Top:
393 0 : return QRect(0, 0, frameWidth, frameHeight / 2);
394 0 : case Location::Bottom:
395 0 : return QRect(0, frameHeight / 2, frameWidth, frameHeight / 2);
396 0 : case Location::Left:
397 0 : return QRect(0, 0, frameWidth / 2, frameHeight);
398 0 : case Location::Right:
399 0 : return QRect(frameWidth / 2, 0, frameWidth / 2, frameHeight);
400 0 : case Location::Center:
401 0 : return fullFrameHighlightingRect;
402 0 : case Location::Undefined:
403 0 : break;
404 : }
405 :
406 0 : return QRect();
407 : }
408 :
409 0 : IDockWindow* DropController::dockWindow() const
410 : {
411 0 : return dockWindowProvider()->window();
412 : }
413 :
414 0 : DockPageView* DropController::currentPage() const
415 : {
416 0 : return dockWindow() ? dockWindow()->currentPage() : nullptr;
417 : }
418 :
419 0 : DockBase* DropController::draggedDock() const
420 : {
421 0 : auto windowBeingDragged = KDDockWidgets::Core::DragController::instance(iocContext()->id)->windowBeingDragged();
422 0 : if (!windowBeingDragged || windowBeingDragged->dockWidgets().isEmpty()) {
423 0 : return nullptr;
424 : }
425 :
426 0 : const DockPageView* page = currentPage();
427 0 : if (!page) {
428 0 : return nullptr;
429 : }
430 :
431 0 : KDDockWidgets::Core::DockWidget* draggedDockWidget = windowBeingDragged->dockWidgets().first();
432 0 : for (DockBase* dock : page->allDocks()) {
433 0 : if (dock->dockWidget() == draggedDockWidget) {
434 0 : return dock;
435 : }
436 0 : }
437 :
438 0 : return nullptr;
439 : }
|