diff --git a/src/app/ui/data_recovery_view.cpp b/src/app/ui/data_recovery_view.cpp index e6a0bdc25..f39887c0f 100644 --- a/src/app/ui/data_recovery_view.cpp +++ b/src/app/ui/data_recovery_view.cpp @@ -181,20 +181,10 @@ TabIcon DataRecoveryView::getTabIcon() return TabIcon::NONE; } -WorkspaceView* DataRecoveryView::cloneWorkspaceView() -{ - return nullptr; // This view cannot be cloned -} - void DataRecoveryView::onWorkspaceViewSelected() { } -void DataRecoveryView::onClonedFrom(WorkspaceView* from) -{ - ASSERT(false); // Never called -} - bool DataRecoveryView::onCloseView(Workspace* workspace) { workspace->removeView(this); diff --git a/src/app/ui/data_recovery_view.h b/src/app/ui/data_recovery_view.h index 346a23e2d..0fe3c4d74 100644 --- a/src/app/ui/data_recovery_view.h +++ b/src/app/ui/data_recovery_view.h @@ -33,9 +33,7 @@ namespace app { // WorkspaceView implementation ui::Widget* getContentWidget() override { return this; } - WorkspaceView* cloneWorkspaceView() override; void onWorkspaceViewSelected() override; - void onClonedFrom(WorkspaceView* from) override; bool onCloseView(Workspace* workspace) override; void onTabPopup(Workspace* workspace) override; diff --git a/src/app/ui/devconsole_view.cpp b/src/app/ui/devconsole_view.cpp index ca47f6a9c..6ac599063 100644 --- a/src/app/ui/devconsole_view.cpp +++ b/src/app/ui/devconsole_view.cpp @@ -108,11 +108,6 @@ void DevConsoleView::onWorkspaceViewSelected() m_entry->requestFocus(); } -void DevConsoleView::onClonedFrom(WorkspaceView* from) -{ - // Do nothing -} - bool DevConsoleView::onCloseView(Workspace* workspace) { workspace->removeView(this); diff --git a/src/app/ui/devconsole_view.h b/src/app/ui/devconsole_view.h index 5f84feb6b..0f9572812 100644 --- a/src/app/ui/devconsole_view.h +++ b/src/app/ui/devconsole_view.h @@ -30,9 +30,9 @@ namespace app { // WorkspaceView implementation ui::Widget* getContentWidget() override { return this; } + bool canCloneWorkspaceView() override { return true; } WorkspaceView* cloneWorkspaceView() override; void onWorkspaceViewSelected() override; - void onClonedFrom(WorkspaceView* from) override; bool onCloseView(Workspace* workspace) override; void onTabPopup(Workspace* workspace) override; diff --git a/src/app/ui/document_view.h b/src/app/ui/document_view.h index c303c364e..40e6b59ac 100644 --- a/src/app/ui/document_view.h +++ b/src/app/ui/document_view.h @@ -48,6 +48,7 @@ namespace app { // WorkspaceView implementation ui::Widget* getContentWidget() override { return this; } + bool canCloneWorkspaceView() override { return true; } WorkspaceView* cloneWorkspaceView() override; void onWorkspaceViewSelected() override; void onClonedFrom(WorkspaceView* from) override; diff --git a/src/app/ui/home_view.cpp b/src/app/ui/home_view.cpp index bc429a801..7209b58b6 100644 --- a/src/app/ui/home_view.cpp +++ b/src/app/ui/home_view.cpp @@ -86,16 +86,6 @@ TabIcon HomeView::getTabIcon() return TabIcon::HOME; } -WorkspaceView* HomeView::cloneWorkspaceView() -{ - return nullptr; // This view cannot be cloned -} - -void HomeView::onClonedFrom(WorkspaceView* from) -{ - ASSERT(false); // Never called -} - bool HomeView::onCloseView(Workspace* workspace) { workspace->removeView(this); diff --git a/src/app/ui/home_view.h b/src/app/ui/home_view.h index 1cd4dea9e..f1241fb64 100644 --- a/src/app/ui/home_view.h +++ b/src/app/ui/home_view.h @@ -50,8 +50,6 @@ namespace app { // WorkspaceView implementation ui::Widget* getContentWidget() override { return this; } - WorkspaceView* cloneWorkspaceView() override; - void onClonedFrom(WorkspaceView* from) override; bool onCloseView(Workspace* workspace) override; void onTabPopup(Workspace* workspace) override; void onWorkspaceViewSelected() override; diff --git a/src/app/ui/main_window.cpp b/src/app/ui/main_window.cpp index 1cb6b6a7f..1b70d822d 100644 --- a/src/app/ui/main_window.cpp +++ b/src/app/ui/main_window.cpp @@ -253,7 +253,7 @@ void MainWindow::onActiveViewChange() configureWorkspaceLayout(); } -bool MainWindow::onIsModified(Tabs* tabs, TabView* tabView) +bool MainWindow::isModifiedTab(Tabs* tabs, TabView* tabView) { if (DocumentView* docView = dynamic_cast(tabView)) { Document* document = docView->getDocument(); @@ -264,6 +264,14 @@ bool MainWindow::onIsModified(Tabs* tabs, TabView* tabView) } } +bool MainWindow::canCloneTab(Tabs* tabs, TabView* tabView) +{ + ASSERT(tabView) + + WorkspaceView* view = dynamic_cast(tabView); + return view->canCloneWorkspaceView(); +} + void MainWindow::onSelectTab(Tabs* tabs, TabView* tabView) { if (!tabView) @@ -282,6 +290,15 @@ void MainWindow::onCloseTab(Tabs* tabs, TabView* tabView) m_workspace->closeView(view); } +void MainWindow::onCloneTab(Tabs* tabs, TabView* tabView, int pos) +{ + WorkspaceView* view = dynamic_cast(tabView); + WorkspaceView* copy = view->cloneWorkspaceView(); + ASSERT(copy); + m_workspace->addViewToPanel( + static_cast(tabs)->panel(), copy, true, pos); +} + void MainWindow::onContextMenuTab(Tabs* tabs, TabView* tabView) { WorkspaceView* view = dynamic_cast(tabView); @@ -315,14 +332,19 @@ void MainWindow::onDockingTab(Tabs* tabs, TabView* tabView) m_workspace->removeDropViewPreview(); } -DropTabResult MainWindow::onDropTab(Tabs* tabs, TabView* tabView, const gfx::Point& pos) +DropTabResult MainWindow::onDropTab(Tabs* tabs, TabView* tabView, const gfx::Point& pos, bool clone) { m_workspace->removeDropViewPreview(); - if (m_workspace->dropViewAt(pos, dynamic_cast(tabView))) - return DropTabResult::DOCKED_IN_OTHER_PLACE; + DropViewAtResult result = + m_workspace->dropViewAt(pos, dynamic_cast(tabView), clone); + + if (result == DropViewAtResult::MOVED_TO_OTHER_PANEL) + return DropTabResult::REMOVE; + else if (result == DropViewAtResult::CLONED_VIEW) + return DropTabResult::DONT_REMOVE; else - return DropTabResult::IGNORE; + return DropTabResult::NOT_HANDLED; } void MainWindow::configureWorkspaceLayout() diff --git a/src/app/ui/main_window.h b/src/app/ui/main_window.h index 2a2fed4f8..7800705d6 100644 --- a/src/app/ui/main_window.h +++ b/src/app/ui/main_window.h @@ -80,14 +80,16 @@ namespace app { void showDataRecovery(crash::DataRecovery* dataRecovery); // TabsDelegate implementation. - bool onIsModified(Tabs* tabs, TabView* tabView) override; + bool isModifiedTab(Tabs* tabs, TabView* tabView) override; + bool canCloneTab(Tabs* tabs, TabView* tabView) override; void onSelectTab(Tabs* tabs, TabView* tabView) override; void onCloseTab(Tabs* tabs, TabView* tabView) override; + void onCloneTab(Tabs* tabs, TabView* tabView, int pos) override; void onContextMenuTab(Tabs* tabs, TabView* tabView) override; void onMouseOverTab(Tabs* tabs, TabView* tabView) override; DropViewPreviewResult onFloatingTab(Tabs* tabs, TabView* tabView, const gfx::Point& pos) override; void onDockingTab(Tabs* tabs, TabView* tabView) override; - DropTabResult onDropTab(Tabs* tabs, TabView* tabView, const gfx::Point& pos) override; + DropTabResult onDropTab(Tabs* tabs, TabView* tabView, const gfx::Point& pos, bool clone) override; protected: bool onProcessMessage(ui::Message* msg) override; diff --git a/src/app/ui/tabs.cpp b/src/app/ui/tabs.cpp index 0c460996b..255604ca8 100644 --- a/src/app/ui/tabs.cpp +++ b/src/app/ui/tabs.cpp @@ -48,14 +48,15 @@ WidgetType Tabs::Type() Tabs::Tabs(TabsDelegate* delegate) : Widget(Tabs::Type()) , m_border(2) + , m_docked(false) , m_hot(nullptr) , m_hotCloseButton(false) , m_clickedCloseButton(false) , m_selected(nullptr) - , m_docked(false) , m_delegate(delegate) , m_removedTab(nullptr) , m_isDragging(false) + , m_dragCopy(false) , m_dragTab(nullptr) , m_floatingTab(nullptr) , m_floatingOverlay(nullptr) @@ -99,7 +100,7 @@ void Tabs::addTab(TabView* tabView, bool from_drop, int pos) tab->oldX = (from_drop ? m_dropNewPosX-tab->width/2: tab->x); tab->oldWidth = tab->width; - tab->modified = (m_delegate ? m_delegate->onIsModified(this, tabView): false); + tab->modified = (m_delegate ? m_delegate->isModifiedTab(this, tabView): false); } void Tabs::removeTab(TabView* tabView, bool with_animation) @@ -130,7 +131,7 @@ void Tabs::removeTab(TabView* tabView, bool with_animation) if (with_animation) { if (m_delegate) - tab->modified = m_delegate->onIsModified(this, tabView); + tab->modified = m_delegate->isModifiedTab(this, tabView); tab->view = nullptr; // The view will be destroyed after Tabs::removeTab() anyway resetOldPositions(); @@ -154,13 +155,17 @@ void Tabs::updateTabs() int i = 0; for (auto& tab : m_list) { - if (tab == m_floatingTab) { + if (tab == m_floatingTab && !m_dragCopy) { ++i; continue; } - if (m_dropNewTab && m_dropNewIndex == i) + if ((m_dropNewTab && m_dropNewIndex == i) || + (m_dragTab && !m_floatingTab && + m_dragCopy && + m_dragCopyIndex == i)) { x += tabWidth; + } tab->text = tab->view->getTabText(); tab->icon = tab->view->getTabIcon(); @@ -257,17 +262,15 @@ void Tabs::setDropViewPreview(const gfx::Point& pos, TabView* view) else newIndex = 0; - bool startAni = (m_dropNewIndex != newIndex || m_dropNewTab != view); + bool startAni = (m_dropNewIndex != newIndex || + m_dropNewTab != view); m_dropNewIndex = newIndex; m_dropNewPosX = (pos.x - getBounds().x); m_dropNewTab = view; - if (startAni) { - resetOldPositions(animationTime()); - updateTabs(); - startAnimation(ANI_REORDER_TABS, ANI_REORDER_TABS_TICKS); - } + if (startAni) + startReorderTabsAnimation(); else invalidate(); } @@ -276,9 +279,7 @@ void Tabs::removeDropViewPreview() { m_dropNewTab = nullptr; - resetOldPositions(animationTime()); - updateTabs(); - startAnimation(ANI_REORDER_TABS, ANI_REORDER_TABS_TICKS); + startReorderTabsAnimation(); } bool Tabs::onProcessMessage(Message* msg) @@ -327,7 +328,7 @@ bool Tabs::onProcessMessage(Message* msg) if (result != DropViewPreviewResult::DROP_IN_TABS) { if (!m_floatingOverlay) createFloatingOverlay(m_selected.get()); - m_floatingOverlay->moveOverlay(mousePos - m_dragOffset); + m_floatingOverlay->moveOverlay(mousePos - m_floatingOffset); } else { destroyFloatingOverlay(); @@ -343,18 +344,8 @@ bool Tabs::onProcessMessage(Message* msg) // Docked tab if (!m_floatingTab) { - m_selected->x = m_dragTabX + delta.x; - - int i = (mousePos.x - m_border*guiscale() - getBounds().x) / m_selected->width; - i = MID(0, i, int(m_list.size())-1); - if (i != m_dragTabIndex) { - m_list.erase(m_list.begin()+m_dragTabIndex); - m_list.insert(m_list.begin()+i, m_selected); - m_dragTabIndex = i; - - startDockDragTabAnimation(); - } - + m_dragTab->x = m_dragTabX + delta.x; + updateDragTabIndexes(mousePos.x, false); if (justDocked) justDocked->oldX = m_dragTabX + delta.x; } @@ -375,7 +366,7 @@ bool Tabs::onProcessMessage(Message* msg) if (m_hot && !hasCapture()) { MouseMessage* mouseMsg = static_cast(msg); m_dragMousePos = mouseMsg->position(); - m_dragOffset = mouseMsg->position() - + m_floatingOffset = mouseMsg->position() - (getBounds().getOrigin() + getTabBounds(m_hot.get()).getOrigin()); if (m_hotCloseButton) { @@ -415,13 +406,13 @@ bool Tabs::onProcessMessage(Message* msg) } } else { - DropTabResult result = DropTabResult::IGNORE; + DropTabResult result = DropTabResult::NOT_HANDLED; if (m_delegate) { ASSERT(m_selected); - result = - m_delegate->onDropTab(this, m_selected->view, - mouseMsg->position()); + result = m_delegate->onDropTab( + this, m_selected->view, + mouseMsg->position(), m_dragCopy); } stopDrag(result); @@ -446,6 +437,25 @@ bool Tabs::onProcessMessage(Message* msg) return true; } + case kKeyDownMessage: + case kKeyUpMessage: { + TabPtr tab = (m_isDragging ? m_dragTab: m_hot); + + bool oldDragCopy = m_dragCopy; + m_dragCopy = ((msg->ctrlPressed() || msg->altPressed()) && + (tab && m_delegate && m_delegate->canCloneTab(this, tab->view))); + + if (oldDragCopy != m_dragCopy) { + updateDragTabIndexes(get_mouse_position().x, true); + updateMouseCursor(); + } + break; + } + + case kSetCursorMessage: + updateMouseCursor(); + return true; + } return Widget::onProcessMessage(msg); @@ -465,13 +475,24 @@ void Tabs::onPaint(PaintEvent& ev) // For each tab... for (TabPtr& tab : m_list) { - if (tab == m_floatingTab) + if (tab == m_floatingTab && !m_dragCopy) continue; box = getTabBounds(tab.get()); - if (tab != m_selected) - drawTab(g, box, tab.get(), 0, (tab == m_hot), false); + if ((!m_dragTab) || + (tab->view != m_dragTab->view) || + (m_dragCopy)) { + int dy = 0; + if (animation() == ANI_ADDING_TAB && tab == m_selected) { + double t = animationTime(); + dy = int(box.h - box.h * t); + } + + drawTab(g, box, tab.get(), dy, + (tab == m_hot), + (tab == m_selected)); + } box.x = box.x2(); } @@ -486,24 +507,16 @@ void Tabs::onPaint(PaintEvent& ev) } // Tab that is being dragged - if (m_selected && m_selected != m_floatingTab) { - double t = animationTime(); - TabPtr tab(m_selected); + if (m_dragTab && !m_floatingTab) { + TabPtr tab(m_dragTab); box = getTabBounds(tab.get()); - - int dy = 0; - if (animation() == ANI_ADDING_TAB) - dy = int(box.h - box.h * t); - - drawTab(g, box, m_selected.get(), dy, (tab == m_hot), true); + drawTab(g, box, tab.get(), 0, true, true); } // New tab from other Tab that want to be dropped here. if (m_dropNewTab) { SkinTheme* theme = static_cast(this->getTheme()); Tab newTab(m_dropNewTab); - newTab.text = m_dropNewTab->getTabText(); - newTab.icon = m_dropNewTab->getTabIcon(); newTab.width = newTab.oldWidth = (!m_list.empty() ? m_list[0]->width: @@ -608,7 +621,7 @@ void Tabs::drawTab(Graphics* g, const gfx::Rect& _box, if (m_delegate) { if (tab->view) - tab->modified = m_delegate->onIsModified(this, tab->view); + tab->modified = m_delegate->isModifiedTab(this, tab->view); if (tab->modified && (!hover || !m_hotCloseButton)) { @@ -764,9 +777,13 @@ void Tabs::startDrag() updateTabs(); m_isDragging = true; - m_dragTab = m_selected; + m_dragCopy = false; + m_dragTab.reset(new Tab(m_selected->view)); + m_dragTab->x = m_selected->x; + m_dragTab->width = m_selected->width; m_dragTabX = m_selected->x; - m_dragTabIndex = std::find(m_list.begin(), m_list.end(), m_selected) - m_list.begin(); + m_dragTabIndex = + m_dragCopyIndex = std::find(m_list.begin(), m_list.end(), m_selected) - m_list.begin(); EditorView::SetScrollUpdateMethod(EditorView::KeepCenter); } @@ -774,35 +791,65 @@ void Tabs::startDrag() void Tabs::stopDrag(DropTabResult result) { m_isDragging = false; + ASSERT(m_dragTab); switch (result) { - case DropTabResult::IGNORE: + case DropTabResult::NOT_HANDLED: + case DropTabResult::DONT_REMOVE: { destroyFloatingTab(); - ASSERT(m_selected); - if (m_selected) { - m_selected->oldX = m_selected->x; - m_selected->oldWidth = m_selected->width; + bool localCopy = false; + if (result == DropTabResult::NOT_HANDLED && + m_dragTab && m_dragCopy && m_delegate) { + ASSERT(m_dragCopyIndex >= 0); + + m_delegate->onCloneTab(this, m_dragTab->view, m_dragCopyIndex); + + // To animate the new tab created by onCloneTab() from the + // m_dragTab position. + m_list[m_dragCopyIndex]->oldX = m_dragTab->x; + m_list[m_dragCopyIndex]->oldWidth = m_dragTab->width; + localCopy = true; } - resetOldPositions(animationTime()); - updateTabs(); - startAnimation(ANI_REORDER_TABS, ANI_REORDER_TABS_TICKS); - break; - case DropTabResult::DOCKED_IN_OTHER_PLACE: { - TabPtr tab = m_dragTab; + m_dragCopy = false; + m_dragCopyIndex = -1; - m_floatingTab.reset(); - m_removedTab.reset(); - destroyFloatingTab(); + startReorderTabsAnimation(); - ASSERT(tab.get()); - if (tab) - removeTab(tab->view, false); + if (m_selected && m_dragTab && !localCopy) { + if (result == DropTabResult::NOT_HANDLED) { + // To animate m_selected tab from the m_dragTab position + // when we drop the tab in the same Tabs (with no copy) + m_selected->oldX = m_dragTab->x; + m_selected->oldWidth = m_dragTab->width; + } + else { + ASSERT(result == DropTabResult::DONT_REMOVE); + + // In this case the tab was copied to other Tabs, so we + // avoid any kind of animation for the m_selected (it stays + // were it's). + m_selected->oldX = m_selected->x; + m_selected->oldWidth = m_selected->width; + } + } break; } + case DropTabResult::REMOVE: + m_floatingTab.reset(); + m_removedTab.reset(); + m_dragCopy = false; + m_dragCopyIndex = -1; + destroyFloatingTab(); + + ASSERT(m_dragTab.get()); + if (m_dragTab) + removeTab(m_dragTab->view, false); + break; + } m_dragTab.reset(); @@ -831,7 +878,7 @@ gfx::Rect Tabs::getTabBounds(Tab* tab) return box; } -void Tabs::startDockDragTabAnimation() +void Tabs::startReorderTabsAnimation() { resetOldPositions(animationTime()); updateTabs(); @@ -903,4 +950,39 @@ void Tabs::destroyFloatingOverlay() } } +void Tabs::updateMouseCursor() +{ + if (m_dragCopy) + ui::set_mouse_cursor(kArrowPlusCursor); + else + ui::set_mouse_cursor(kArrowCursor); +} + +void Tabs::updateDragTabIndexes(int mouseX, bool startAni) +{ + if (m_dragTab) { + int i = (mouseX - m_border*guiscale() - getBounds().x) / m_dragTab->width; + + if (m_dragCopy) { + i = MID(0, i, int(m_list.size())); + if (i != m_dragCopyIndex) { + m_dragCopyIndex = i; + startAni = true; + } + } + else if (hasMouseOver()) { + i = MID(0, i, int(m_list.size())-1); + if (i != m_dragTabIndex) { + m_list.erase(m_list.begin()+m_dragTabIndex); + m_list.insert(m_list.begin()+i, m_selected); + m_dragTabIndex = i; + startAni = true; + } + } + } + + if (startAni) + startReorderTabsAnimation(); +} + } // namespace app diff --git a/src/app/ui/tabs.h b/src/app/ui/tabs.h index 66e6afe9c..7c723d209 100644 --- a/src/app/ui/tabs.h +++ b/src/app/ui/tabs.h @@ -45,8 +45,18 @@ namespace app { }; enum class DropTabResult { - IGNORE, - DOCKED_IN_OTHER_PLACE, + // The operation should be handled inside the Tabs widget (if the + // tab was dropped in the same Tabs, it will be moved or copied + // depending on what the user wants). + NOT_HANDLED, + + // The tab was docked in other place, so it must be removed from + // the specific Tabs widget. + REMOVE, + + // The operation was already handled, but the tab must not be + // removed from the Tabs (e.g. because it was cloned). + DONT_REMOVE, }; enum class DropViewPreviewResult { @@ -62,7 +72,10 @@ namespace app { virtual ~TabsDelegate() { } // Returns true if the tab represent a modified document. - virtual bool onIsModified(Tabs* tabs, TabView* tabView) = 0; + virtual bool isModifiedTab(Tabs* tabs, TabView* tabView) = 0; + + // Returns true if the tab can be cloned. + virtual bool canCloneTab(Tabs* tabs, TabView* tabView) = 0; // Called when the user selected the tab with the left mouse button. virtual void onSelectTab(Tabs* tabs, TabView* tabView) = 0; @@ -70,6 +83,10 @@ namespace app { // When the tab close button is pressed (or middle mouse button is used to close it). virtual void onCloseTab(Tabs* tabs, TabView* tabView) = 0; + // When the tab is cloned (this only happens when the user + // drag-and-drop the tab into the same Tabs with the Ctrl key). + virtual void onCloneTab(Tabs* tabs, TabView* tabView, int pos) = 0; + // When the right-click is pressed in the tab. virtual void onContextMenuTab(Tabs* tabs, TabView* tabView) = 0; @@ -84,7 +101,7 @@ namespace app { // Called when the user is dragging a tab inside the Tabs bar. virtual void onDockingTab(Tabs* tabs, TabView* tabView) = 0; - virtual DropTabResult onDropTab(Tabs* tabs, TabView* tabView, const gfx::Point& pos) = 0; + virtual DropTabResult onDropTab(Tabs* tabs, TabView* tabView, const gfx::Point& pos, bool clone) = 0; }; // Tabs control. Used to show opened documents. @@ -99,6 +116,9 @@ namespace app { bool modified; Tab(TabView* view) : view(view) { + ASSERT(view); + text = view->getTabText(); + icon = view->getTabIcon(); } }; @@ -165,23 +185,35 @@ namespace app { void startDrag(); void stopDrag(DropTabResult result); gfx::Rect getTabBounds(Tab* tab); - void startDockDragTabAnimation(); + void startReorderTabsAnimation(); void startRemoveDragTabAnimation(); void createFloatingOverlay(Tab* tab); void destroyFloatingTab(); void destroyFloatingOverlay(); + void updateMouseCursor(); + void updateDragTabIndexes(int mouseX, bool force_animation); - int m_border; + // Specific variables about the style + int m_border; // Pixels used from the left side to draw the first tab + bool m_docked; // True if tabs are inside the workspace (not the main tabs panel) + int m_tabsHeight; // Number of pixels in Y-axis for each Tab + int m_tabsBottomHeight; // Number of pixels in the bottom part of Tabs widget + + // List of tabs (pointers to Tab instances). TabsList m_list; - TabPtr m_hot; - bool m_hotCloseButton; - bool m_clickedCloseButton; - TabPtr m_selected; - // Style - bool m_docked; - int m_tabsHeight; - int m_tabsBottomHeight; + // Which tab has the mouse over. + TabPtr m_hot; + + // True if the mouse is above the close button of m_hot tab. + bool m_hotCloseButton; + + // True if the user clicked over the close button of m_hot. + bool m_clickedCloseButton; + + // Current active tab. When this tab changes, the + // TabsDelegate::onSelectTab() is called. + TabPtr m_selected; // Delegate of notifications TabsDelegate* m_delegate; @@ -189,17 +221,68 @@ namespace app { // Variables for animation purposes TabPtr m_removedTab; + //////////////////////////////////////// // Drag-and-drop + + // True when the user is dragging a tab (the mouse must be + // captured when this flag is true). The dragging process doesn't + // start immediately, when the user clicks a tab the m_selected + // tab is changed, and then there is a threshold after we start + // dragging the tab. bool m_isDragging; + + // True when the user is dragging the tab as a copy inside this + // same Tabs (e.g. using Ctrl key) This can be true only if + // TabsDelegate::canCloneTab() returns true for the tab being + // dragged. + bool m_dragCopy; + + // A copy of m_selected used only in the dragging process. This + // tab is not pointing to the same tab as m_selected. It's a copy + // because if m_dragCopy is true, we've to paint m_selected and + // m_dragTab separately (as two different tabs). TabPtr m_dragTab; + + // Initial X position where m_dragTab/m_selected was when we + // started (mouse down) the drag process. int m_dragTabX; + + // Initial mouse position when we start the dragging process. gfx::Point m_dragMousePos; - gfx::Point m_dragOffset; + + // New position where m_selected is being dragged. It's used to + // change the position of m_selected inside the m_list vector in + // real-time while the mouse is moved. int m_dragTabIndex; + + // New position where a copyt of m_dragTab will be dropped. It + // only makes sense when m_dragCopy is true (the user is pressing + // Ctrl key and want to create a copy of the tab). + int m_dragCopyIndex; + + // Null if we are dragging the m_dragTab inside this Tabs widget, + // or non-null (equal to m_selected indeed), if we're moving the + // tab outside the Tabs widget (e.g. to dock the tabs in other + // location). TabPtr m_floatingTab; + + // Overlay used to show the floating tab outside the Tabs widget + // (this overlay floats next to the mouse cursor). It's destroyed + // and recreated every time the tab is put inside or outside the + // Tabs widget. base::UniquePtr m_floatingOverlay; + // Relative mouse position inside the m_dragTab (used to adjust + // the m_floatingOverlay precisely). + gfx::Point m_floatingOffset; + + //////////////////////////////////////// // Drop new tabs + + // Non-null when a foreign tab (a "TabView" from other "Tabs" + // widget), will be dropped in this specific Tabs widget. It's + // used only for feedback/UI purposes when the + // setDropViewPreview() is called. TabView* m_dropNewTab; int m_dropNewIndex; int m_dropNewPosX; diff --git a/src/app/ui/workspace.cpp b/src/app/ui/workspace.cpp index da41eb1e0..343d6f7d1 100644 --- a/src/app/ui/workspace.cpp +++ b/src/app/ui/workspace.cpp @@ -198,14 +198,14 @@ void Workspace::removeDropViewPreview() } } -bool Workspace::dropViewAt(const gfx::Point& pos, WorkspaceView* view) +DropViewAtResult Workspace::dropViewAt(const gfx::Point& pos, WorkspaceView* view, bool clone) { WorkspaceTabs* tabs = getTabsAt(pos); WorkspacePanel* panel = getPanelAt(pos); if (panel) { // Create new panel - return panel->dropViewAt(pos, getViewPanel(view), view); + return panel->dropViewAt(pos, getViewPanel(view), view, clone); } else if (tabs && tabs != getViewPanel(view)->tabs()) { // Dock tab in other tabs @@ -213,13 +213,22 @@ bool Workspace::dropViewAt(const gfx::Point& pos, WorkspaceView* view) ASSERT(dropPanel); int pos = tabs->getDropTabIndex(); + DropViewAtResult result; + + if (clone) { + view = view->cloneWorkspaceView(); + result = DropViewAtResult::CLONED_VIEW; + } + else { + removeView(view); + result = DropViewAtResult::MOVED_TO_OTHER_PANEL; + } - removeView(view); addViewToPanel(dropPanel, view, true, pos); - return true; + return result; } else - return false; + return DropViewAtResult::NOTHING; } void Workspace::addViewToPanel(WorkspacePanel* panel, WorkspaceView* view, bool from_drop, int pos) diff --git a/src/app/ui/workspace.h b/src/app/ui/workspace.h index f20280817..7e90b1159 100644 --- a/src/app/ui/workspace.h +++ b/src/app/ui/workspace.h @@ -32,6 +32,7 @@ namespace app { iterator end() { return m_views.end(); } void addView(WorkspaceView* view, int pos = -1); + void addViewToPanel(WorkspacePanel* panel, WorkspaceView* view, bool from_drop, int pos); void removeView(WorkspaceView* view); // Closes the given view. Returns false if the user cancels the @@ -53,7 +54,7 @@ namespace app { void removeDropViewPreview(); // Returns true if the view was docked inside the workspace. - bool dropViewAt(const gfx::Point& pos, WorkspaceView* view); + DropViewAtResult dropViewAt(const gfx::Point& pos, WorkspaceView* view, bool clone); Signal0 ActiveViewChanged; @@ -62,7 +63,6 @@ namespace app { void onResize(ui::ResizeEvent& ev) override; private: - void addViewToPanel(WorkspacePanel* panel, WorkspaceView* view, bool from_drop, int pos); WorkspacePanel* getViewPanel(WorkspaceView* view); WorkspacePanel* getPanelAt(const gfx::Point& pos); WorkspaceTabs* getTabsAt(const gfx::Point& pos); diff --git a/src/app/ui/workspace_panel.cpp b/src/app/ui/workspace_panel.cpp index b1152880e..4d1134a33 100644 --- a/src/app/ui/workspace_panel.cpp +++ b/src/app/ui/workspace_panel.cpp @@ -225,24 +225,33 @@ void WorkspacePanel::adjustTime(int& time, int flag) --time; } -bool WorkspacePanel::dropViewAt(const gfx::Point& pos, WorkspacePanel* from, WorkspaceView* view) +DropViewAtResult WorkspacePanel::dropViewAt(const gfx::Point& pos, WorkspacePanel* from, WorkspaceView* view, bool clone) { int dropArea = calculateDropArea(pos); if (!dropArea) - return false; + return DropViewAtResult::NOTHING; // If we're dropping the view in the same panel, and it's the only // view in the panel: We cannot drop the view in the panel (because // if we remove the last view, the panel will be destroyed). - if (from == this && m_views.size() == 1) - return false; + if (!clone && from == this && m_views.size() == 1) + return DropViewAtResult::NOTHING; int splitterAlign = 0; if (dropArea & (JI_LEFT | JI_RIGHT)) splitterAlign = JI_HORIZONTAL; else if (dropArea & (JI_TOP | JI_BOTTOM)) splitterAlign = JI_VERTICAL; ASSERT(from); - from->removeView(view); + DropViewAtResult result; + Workspace* workspace = getWorkspace(); + if (clone) { + view = view->cloneWorkspaceView(); + result = DropViewAtResult::CLONED_VIEW; + } + else { + workspace->removeView(view); + result = DropViewAtResult::MOVED_TO_OTHER_PANEL; + } WorkspaceTabs* newTabs = new WorkspaceTabs(m_tabs->getDelegate()); WorkspacePanel* newPanel = new WorkspacePanel(SUB_PANEL); @@ -294,9 +303,9 @@ bool WorkspacePanel::dropViewAt(const gfx::Point& pos, WorkspacePanel* from, Wor break; } - newPanel->addView(view, true); + workspace->addViewToPanel(newPanel, view, true, -1); parent->layout(); - return true; + return result; } int WorkspacePanel::calculateDropArea(const gfx::Point& pos) const diff --git a/src/app/ui/workspace_panel.h b/src/app/ui/workspace_panel.h index 939db96fb..70533a61a 100644 --- a/src/app/ui/workspace_panel.h +++ b/src/app/ui/workspace_panel.h @@ -21,6 +21,12 @@ namespace app { class Workspace; class WorkspaceTabs; + enum class DropViewAtResult { + NOTHING, + MOVED_TO_OTHER_PANEL, + CLONED_VIEW, + }; + class WorkspacePanel : public ui::Widget , public AnimatedWidget { enum Ani : int { @@ -60,7 +66,7 @@ namespace app { void removeDropViewPreview(); // Returns true if the view was docked inside the panel. - bool dropViewAt(const gfx::Point& pos, WorkspacePanel* from, WorkspaceView* view); + DropViewAtResult dropViewAt(const gfx::Point& pos, WorkspacePanel* from, WorkspaceView* view, bool clone); protected: void onPaint(ui::PaintEvent& ev) override; diff --git a/src/app/ui/workspace_view.h b/src/app/ui/workspace_view.h index ebfe23cb7..3eeb9b072 100644 --- a/src/app/ui/workspace_view.h +++ b/src/app/ui/workspace_view.h @@ -21,13 +21,18 @@ namespace app { virtual ~WorkspaceView() { } virtual ui::Widget* getContentWidget() = 0; - virtual WorkspaceView* cloneWorkspaceView() = 0; - virtual void onWorkspaceViewSelected() = 0; + + virtual bool canCloneWorkspaceView() { return false; } + virtual WorkspaceView* cloneWorkspaceView() { return nullptr; } // Called after the view is added in the correct position inside // the workspace. It can be used to copy/clone scroll position // from the original view. - virtual void onClonedFrom(WorkspaceView* from) = 0; + virtual void onClonedFrom(WorkspaceView* from) { + // Do nothing + } + + virtual void onWorkspaceViewSelected() = 0; // Returns true if the view was closed successfully or false if // the user cancels the operation.